为什么unique_ptr向量是存储指针的首选方法

Why is vector of unique_ptr the prefered way to store pointers?

本文关键字:指针 方法 存储 unique ptr 向量 为什么      更新时间:2023-10-16

我读到的说,制作拥有指针的指针向量的常用方法,例如用于简单用途的指针MyObject,是vector<unique_pointer<MyObject>>

但是每次我们访问一个元素时都会调用unique_ptr::get().还有一点开销。

如果存在这样的东西(我没有使用分配器),为什么带有"自定义删除器"的指针向量不更标准?也就是说,智能向量而不是智能指针的向量。它将消除使用unique_ptr::get()的少量开销。

类似vector<MyObject*, delete_on_destroy_allocator<MyObject>>unique_vector<MyObject>.

向量将采用行为"销毁时删除指针",而不是在每个unique_ptr中复制此行为,是否有原因,或者只是开销可以忽略不计?

如果存在这样的事情,为什么指针的向量没有"自定义删除器">

因为这样的东西不存在,也不可能存在

提供给容器的分配器用于为容器分配内存,并(可选)创建/销毁该容器中的对象。vector<T*>是指针的容器;因此,分配器为指针分配内存并(可选)创建/销毁指针。它不对指针的内容负责:它指向的对象。这是用户要提供和管理的域。

如果分配器负责销毁所指向的对象,那么它在逻辑上也必须负责创建所指向的对象,是吗?毕竟,如果没有,并且我们复制了这样的vector<T*, owning_allocator>,每个副本都会期望销毁指向的对象。但是由于它们指向相同的对象(复制vector<T>复制T),因此您将获得双重销毁。

因此,如果owning_allocator::destruct要删除内存,owning_allocator::construct还必须创建指向的对象。

那么...这有什么作用:

vector<T*, owning_allocator> vec;
vec.push_back(new T());

看到问题了吗?allocator::construct无法决定何时创建T,何时不创建。它不知道调用它是因为vector复制操作还是因为push_back是使用用户创建的T*调用的。它所知道的是它被调用T*值(技术上是对T*的引用,但这无关紧要,因为在这两种情况下都会使用这样的引用调用它)。

因此,它要么 1) 分配一个新对象(通过从给定的指针的副本初始化),要么 2) 复制指针值。而且由于它无法检测到哪种情况在起作用,因此它必须始终选择相同的选项。如果是#1,那么上面的代码就是内存泄漏,因为向量没有存储new T(),也没有其他人删除它。如果它做了#2,那么你就不能复制这样的向量(内部向量重新分配的故事同样模糊)。

你想要的是不可能的。

vector<T>T的容器,无论T是什么。它把T当作它是什么;此值的任何含义都取决于用户。所有权语义是这个含义的一部分。

T*没有所有权语义,因此vector<T*>也没有所有权语义。unique_ptr<T>具有所有权语义,因此vector<unique_ptr<T>>也有所有权语义。

这就是为什么 Boost 有ptr_vector<T>,它明确地是一个矢量样式的类,专门包含指向Ts 的指针。因此,它的界面略有修改;如果你给它一个T*,它知道它正在采用T*并会摧毁它。如果你给它一个T,那么它会分配一个新的T并将值复制/移动到新分配的T中。这是一个不同的容器,具有不同的接口和不同的行为;因此,它值得与vector<T*>不同的类型

unique_ptr的向量和纯指针的向量都不是存储数据的首选方式。在您的示例中:std::vector<MyObject>通常就可以了,如果您在编译时知道大小,请尝试std::array<int>

如果绝对需要间接引用,也可以考虑std::vector<std::reference_wrapper<MyObject>>。在此处阅读有关引用包装器的信息。

话虽如此...如果您:

  • 需要将载体存储在实际数据以外的其他位置,或
  • 如果MyObject非常大/移动成本很高,或者
  • 如果建造或破坏MyObject有您想要避免的现实世界的副作用;

此外,您希望MyObject不再从向量中引用时被释放 - 唯一指针的向量是相关的。

现在,指针只是从C语言继承的简单数据类型;它没有自定义删除器或自定义任何东西。但是 -std::unique_ptr确实支持自定义删除器。此外,您可能有更复杂的资源管理需求,让每个元素管理自己的分配和取消分配是没有意义的 - 在这种情况下,"智能"向量类可能是相关的。

所以:不同的数据结构适合不同的场景。