[英]Why std::vector does not have a release method?
我發現自己處於一種情況,我希望為std::vector<>
unique_ptr
的release()
模擬。 例如:
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
提取內存緩沖區並直接使用它可能是合理的。 但它不能只是一個指針。 它必須有一個分配器。
我能想到的原因有兩個:
vector
與小對象優化兼容。 也就是說,如果它的尺寸足夠小,它就可以指向自己。 這在 C++11 中被無意中禁用( vector
的移動語義禁止引用/迭代器無效),但它可能會在未來的標准中修復。 所以,歷史上沒有理由提供它,希望將來不會有。這可能會對 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)、 gcc和clang 進行測試。 輸出幾乎如下,還取決於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.