为什么unique_ptr向量是存储指针的首选方法
Why is vector of unique_ptr the prefered way to store pointers?
我读到的说,制作拥有指针的指针向量的常用方法,例如用于简单用途的指针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>
,它明确地是一个矢量样式的类,专门包含指向T
s 的指针。因此,它的界面略有修改;如果你给它一个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
确实支持自定义删除器。此外,您可能有更复杂的资源管理需求,让每个元素管理自己的分配和取消分配是没有意义的 - 在这种情况下,"智能"向量类可能是相关的。
所以:不同的数据结构适合不同的场景。
- 在C#中处理C++指针而不使用unsafe的最佳方法
- C++优先级队列,按对象的唯一指针的特定方法升序排列
- 对于C++中使用智能指针的指针算术限制,有没有一种变通方法
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- 将成员函数指针作为参数传递给模板方法
- 通过函数指针定义类范围之外的方法
- 模板方法访问正向声明的类仅在没有此指针的情况下无法编译
- 如何访问由共享指针保存的类方法?
- 初始化指向类实例的智能指针并访问其方法
- 我无法使用C++指针指向类方法返回的 std::vector
- 如何在工厂方法中返回指向基于基础操作系统的派生类的有效指针
- 有没有将变量名称转换为双指针的简短方法?
- 在自定义 std::vector-like 容器中处理指针和非指针模板类型的最佳方法是什么?
- 如何在成为指向基类的指针后保留对子类方法的使用?
- 从纯虚拟类 (A) 派生的指针无法访问来自纯类 (B) 的重载方法
- 类指针方法崩溃程序
- 证明两指针方法有效(对和)
- Qt 删除指针方法
- 我如何将一些类的指针方法转换为指针函数