簡體   English   中英

為什么 std::vector 沒有 release 方法?

[英]Why std::vector does not have a release method?

我發現自己處於一種情況,我希望為std::vector<> unique_ptrrelease()模擬。 例如:

std::vector<int> v(SOME_SIZE);

//.. performing operations on v

int* data = v.release(); // v.size() is now 0 and the ownership of the internal array is released
functionUsingAndInternallyDeletingRowPointer(data);

不提供這種可能性有什么特別的原因嗎? 這可能會對std::vector的內部實現施加一些限制嗎?

或者有一種方法可以實現這一點,但我很尷尬地錯過了?

函數UsingAndInternallyDeletingRowPointer

這個函數究竟會做什么? 因為該內存是通過調用std::allocator_traits<std::allocator<T>>::allocate ,它期望通過調用std::allocator_traits<std::allocator<T>>::deallocate將其刪除。 此外, vector每個元素都是通過調用std::allocator_traits<std::allocator<T>>::construct ,因此必須通過調用std::allocator_traits<std::allocator<T>>::destroy銷毀std::allocator_traits<std::allocator<T>>::destroy

如果該函數嘗試對該指針執行delete []操作,它將不起作用。 或者至少,它不需要工作。

能夠從vector提取內存緩沖區並直接使用它可能是合理的。 但它不能只是一個指針。 它必須有一個分配器。

這是在N4359中提出的,但事實證明有一些微妙的問題會給調用者帶來負擔以避免不正確的行為(似乎主要與分配器有關)。 可以在此處找到有關困難和可能的替代方案的討論。 它最終被 C++ 標准機構拒絕。 可以在評論這個問題及其答案中找到進一步的討論。

我能想到的原因有兩個:

  1. 最初(C++11 之前), vector與小對象優化兼容。 也就是說,如果它的尺寸足夠小,它就可以指向自己。 這在 C++11 中被無意中禁用( vector的移動語義禁止引用/迭代器無效),但它可能會在未來的標准中修復。 所以,歷史上沒有理由提供它,希望將來不會有。
  2. 分配器。 如果將指針傳遞給帶有它不期望的分配器的向量,則您的函數可能會調用未定義的行為

這可能會對 std::vector 的內部實現施加一些限制嗎?

以下是一些允許這會與以下內容發生沖突的事情的示例:

  • 除特殊情況外,底層內存分配無法通過new T[]獲得,也無法通過delete[]銷毀,因為它們會在已分配但實際上不應包含任何T類型對象的內存上調用構造函數和析構函數。
  • 數組的開頭實際上可能不是內存分配的開頭; 例如,向量可以在數組開始之前存儲簿記信息
  • vector在銷毀時可能不會真正釋放內存; 例如,分配可能來自一個小數組池,實現用於快速創建和銷毀小向量。 (此外,這些數組可能都只是更大數組的切片)

我能夠使用自定義分配器實現檢索當前分配的數組的功能。 下面的代碼展示了這個概念:

#ifdef _MSC_VER 
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <cassert>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <vector>
#include <iostream>

// The requirements for the allocator where taken from Howard Hinnant tutorial:
// https://howardhinnant.github.io/allocator_boilerplate.html

template <typename T>
struct MyAllocation
{
    size_t Size = 0;
    std::unique_ptr<T> Ptr;

    MyAllocation() { }

    MyAllocation(MyAllocation && other) noexcept
        : Ptr(std::move(other.Ptr)), Size(other.Size)
    {
        other.Size = 0;
    }
};

// This allocator keep ownership of the last allocate(n)
template <typename T>
class MyAllocator
{
public:
    using value_type = T;

private:
    // This is the actual allocator class that will be shared
    struct Allocator
    {
        [[nodiscard]] T* allocate(std::size_t n)
        {
            T *ret = new T[n];
            if (!(Current.Ptr == nullptr || CurrentDeallocated))
            {
                // Actually release the ownership of the Current unique pointer
                Current.Ptr.release();
            }

            Current.Ptr.reset(ret);
            Current.Size = n;
            CurrentDeallocated = false;
            return ret;
        }

        void deallocate(T* p, std::size_t n)
        {
            (void)n;
            if (Current.Ptr.get() == p)
            {
                CurrentDeallocated = true;
                return;
            }

            delete[] p;
        }

        MyAllocation<T> Current;
        bool CurrentDeallocated = false;
    };
public:
    MyAllocator()
        : m_allocator(std::make_shared<Allocator>())
    {
        std::cout << "MyAllocator()" << std::endl;
    }

    template<class U>
    MyAllocator(const MyAllocator<U> &rhs) noexcept
    {
        std::cout << "MyAllocator(const MyAllocator<U> &rhs)" << std::endl;
        // Just assume it's a allocator of the same type. This is needed in
        // MSVC STL library because of debug proxy allocators
        // https://github.com/microsoft/STL/blob/master/stl/inc/vector
        m_allocator = reinterpret_cast<const MyAllocator<T> &>(rhs).m_allocator;
    }

    MyAllocator(const MyAllocator &rhs) noexcept
        : m_allocator(rhs.m_allocator)
    {
        std::cout << "MyAllocator(const MyAllocator &rhs)" << std::endl;
    }

public:
    T* allocate(std::size_t n)
    {
        std::cout << "allocate(" << n << ")" << std::endl;
        return m_allocator->allocate(n);
    }

    void deallocate(T* p, std::size_t n)
    {
        std::cout << "deallocate(\"" << p << "\", " << n << ")" << std::endl;
        return m_allocator->deallocate(p, n);
    }

    MyAllocation<T> release()
    {
        if (!m_allocator->CurrentDeallocated)
            throw std::runtime_error("Can't release the ownership if the current pointer has not been deallocated by the container");

        return std::move(m_allocator->Current);
    }

public:
    // This is the instance of the allocator that will be shared
    std::shared_ptr<Allocator> m_allocator;
};

// We assume allocators of different types are never compatible
template <class T, class U>
bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) { return false; }

// We assume allocators of different types are never compatible
template <class T, class U>
bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) { return true; }

int main()
{
    MyAllocator<char> allocator;
    {
        std::vector<char, MyAllocator<char>> test(allocator);
        test.resize(5);
        test.resize(std::strlen("Hello World") + 1);
        std::strcpy(test.data(), "Hello World");
        std::cout << "Current buffer: " << test.data() << std::endl;
        test.pop_back();
        test.push_back('!');
        test.push_back('\0');

        try
        {
            (void)allocator.release();
        }
        catch (...)
        {
            std::cout << "Expected throw on release() while the container has still ownership" << std::endl;
        }
    }

    auto allocation = allocator.release();
    std::cout << "Final buffer: " << allocation.Ptr.get() << std::endl;
    return 0;
}

使用 MSVC15 (VS2017)、 gccclang 進行測試 輸出幾乎如下,還取決於std::vector STL 實現和啟用調試編譯的細微差異:

MyAllocator()
MyAllocator(const MyAllocator &rhs)
allocate(5)
allocate(12)
deallocate("", 5)
Current buffer: Hello World
allocate(18)
deallocate("Hello World!", 12)
Expected throw on release() while the container has still ownership
deallocate("Hello World!", 18)
Final buffer: Hello World!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM