Why std::vector does not have a release method?

爱⌒轻易说出口 提交于 2019-12-19 09:22:06

问题


I found myself in a situation where I would have liked to have an analog of unique_ptr's release() for std::vector<>. E.g.:

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);

Is there a particular reason why this kind of possibility is not provided? May that impose some constraint on std::vector's the internal implementation?

Or there is a way to achieve this that I am embarrassingly missing?


回答1:


May that impose some constraint on std::vector's the internal implementation?

Here are some examples of things that allowing this would conflict with:

  • Barring special cases, the underlying memory allocation cannot be obtained by new T[], nor destroyed by delete[], since these would call constructors and destructors on memory that that is allocated but should not actually contain any objects of type T.
  • The beginning of the array might not actually be the beginning of the memory allocation; e.g. the vector could store bookkeeping information just before the start of the array
  • vector might not actually free the memory when destroyed; e.g. instead the allocation might come from a pool of small arrays that the implementation uses for quickly creating and destroying small vectors. (furthermore, these arrays might all just be slices of a larger array)



回答2:


functionUsingAndInternallyDeletingRowPointer

And what exactly would this function do? Because that memory was allocated by calling std::allocator_traits<std::allocator<T>>::allocate, which expects it to be deleted by calling std::allocator_traits<std::allocator<T>>::deallocate. Furthermore, each element of the vector was constructed with a call to std::allocator_traits<std::allocator<T>>::construct, and therefore must be destroyed by a call to std::allocator_traits<std::allocator<T>>::destroy.

If that function tries to do delete [] on that pointer, it won't work. Or at the very least, it isn't required to work.

It might be reasonable to be able to extract a memory buffer from a vector and use it directly. But it could not be a mere pointer. It would have to have an allocator along with it.




回答3:


There are two reasons I can think of:

  1. originally (pre-C++11), vector was compatible with small object optimization. That is, it could've pointed into itself if its size was small enough. This was inadvertently disabled in C++11 (vector's move semantics forbid invalidating of references/iterators), but it may be fixed in future standards. So, there was no reason to provide it historically, and hopefully there won't be in the future.
  2. allocators. Your function is likely to invoke undefined behaviour if passed a pointer to a vector with allocator it didn't expect



回答4:


This was proposed in N4359, but it turns out there are some subtle issues that place a burdens on the caller to avoid incorrect behavior (mostly related to allocators, it seems). A discussion of the difficulties and possible alternatives can be found here. It was ultimately rejected by the C++ standards body. Further discussion can be found in comments this question and its answers.




回答5:


I was able to implement the functionality to retrieve the current allocated array using a custom allocator. The following code show the concept:

#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;
}

Tested with MSVC15 (VS2017), gcc and clang. The output is pretty much the following, depending also on small differences on STL implementation of std::vector and debug compilation enabled:

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!


来源:https://stackoverflow.com/questions/40514362/why-stdvector-does-not-have-a-release-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!