为什么unique_ptr的vector是存储指针的首选方式?

时间:2019-06-16 14:47:21

标签: c++ vector smart-pointers

我读到的内容是,制作拥有MyObject指针的指针向量的通用方法是vector<unique_pointer<MyObject>>

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

为什么没有带有“ custom deleter”的指针向量,如果存在这样的东西(我没有使用分配器),则更标准吗?即,智能向量而不是智能指针的向量。它将消除使用unique_ptr::get()的小开销。

类似vector<MyObject*, delete_on_destroy_allocator<MyObject>>unique_vector<MyObject>的东西。

向量将采用行为“销毁时删除指针”,而不是在每个unique_ptr中重复此行为,这是有原因的,还是开销可以忽略不计?

2 个答案:

答案 0 :(得分:1)

存储数据的首选方法不是unique_ptr的向量,也不是普通指针的向量。在您的示例中:std::vector<MyObject>通常就很好,如果您知道编译时的大小,请尝试std::array<int>

如果您绝对需要间接引用,也可以考虑使用std::vector<std::reference_wrapper<MyObject>>。了解参考包装here

已经说过……如果您

  • 需要将向量存储在实际数据之外的其他位置,或者
  • 如果MyObject的移动非常大/昂贵,或者
  • 如果您要避免MyObject的构造或破坏具有现实的副作用;

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

现在,指针只是从C语言继承而来的简单数据类型;它没有自定义删除器或任何自定义内容...,但是-std::unique_ptr does support custom deleters。同样,可能是由于您有更复杂的资源管理需求,而让每个元素管理自己的分配和取消分配没有意义-在这种情况下,“智能”向量类可能是相关的。 / p>

因此:不同的数据结构适合不同的情况。

答案 1 :(得分:1)

  

如果存在这样的东西,为什么没有带有“ custom deleter”的指针向量

因为这样的事情不存在并且不能存在。

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

如果分配器负责销毁所指向的对象,那么从逻辑上讲它还必须负责创建所指向的对象,是吗?毕竟,如果没有这样做,并且我们复制了这样的vector<T*, owning_allocator>,则每个副本都会破坏指向的对象。但是由于它们指向相同的对象(复制vector<T>会复制T s),因此会造成双重破坏。

因此,如果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明确地的向量风格的类,专门包含指向T*的指针。因此,它的界面略有修改。如果您将其交给T*,则表明它正在采用T并将销毁它。如果您将其交给T,则它将分配一个新的T并将值复制/移动到新分配的vector<T*>中。这是一个不同的容器,具有不同的接口和不同的行为。因此,它应具有与{{1}}不同的 type