为什么std :: vector没有发布方法?

时间:2016-11-09 19:14:48

标签: c++ vector std

我发现自己处于一种情况,我希望unique_ptr release()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);

为什么没有提供这种可能性有特殊原因吗?可能会对std::vector的内部实施施加一些约束吗?

或者有一种方法可以实现这一点,我很尴尬地失踪了?

5 个答案:

答案 0 :(得分:4)

  

functionUsingAndInternallyDeletingRowPointer

这个功能到底会起什么作用?因为该内存是通过调用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来销毁。

如果该函数试图对该指针执行delete [],那么它就不会起作用。或者至少, 无法工作。

能够从vector中提取内存缓冲区并直接使用它可能是合理的。但它不能仅仅是一个指针。它必须有一个分配器。

答案 1 :(得分:2)

我能想到两个原因:

  1. 最初(pre-C ++ 11),vector与小对象优化兼容。也就是说,如果它的尺寸足够小,它可以指出自己。这在C ++ 11中被无意中禁用(vector的移动语义禁止引用/迭代器无效),但它可能在未来的标准中得到修复。因此,没有理由在历史上提供它,并希望将来不会有。
  2. 分配器。如果通过指向带有分配器的向量传递指针,你的函数可能会调用未定义的行为

答案 2 :(得分:1)

  

可能会对std :: vector的内部实现施加一些约束吗?

以下是一些允许与之相冲突的事例:

  • 除特殊情况外,基础内存分配不能由new T[]获取,也不能被delete[]破坏,因为它们会调用内存上的构造函数和析构函数但实际上不应包含T类型的任何对象。
  • 数组的开头实际上可能不是内存分配的开始;例如向量可以在数组开始之前存储簿记信息
  • vector实际上可能在销毁时没有释放内存;例如相反,分配可能来自实现用于快速创建和销毁小向量的小数组池。 (此外,这些数组可能只是更大数组的切片)

答案 3 :(得分:1)

这是在N4359中提出的,但事实证明存在一些微妙的问题会给调用者带来负担以避免不正确的行为(主要与分配器相关)。可以找到关于困难和可能的替代方案的讨论here。它最终被C ++标准组织拒绝了。可以在评论this question及其答案中找到进一步的讨论。

答案 4 :(得分:0)

我能够使用自定义分配器实现检索当前分配数组的功能。以下代码显示了该概念:

#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!