shared_ptr到数组:应该使用它吗?


172

只是有关的一个小查询shared_ptr

使用shared_ptr指向数组的好习惯吗?例如,

shared_ptr<int> sp(new int[10]);

如果没有,那为什么不呢?我已经知道的一个原因是无法增加/减少shared_ptr。因此,它不能像指向数组的普通指针那样使用。


2
FWIT,您也可以考虑仅使用std::vector。您必须小心使用引用传递数组,以免复制它。访问数据的语法比shared_ptr干净,并且调整其大小非常简单。如果需要,您将获得所有STL优点。
Nicu Stiurca,2012年

6
如果数组的大小是在编译时确定的,则也可以考虑使用std::array。它几乎与原始数组相同,但具有在大多数库组件中使用的正确语义。特别是用delete而不是销毁该类型的对象delete[]。与不同的是vector,它直接将数据存储在对象中,因此您无需额外分配。
celtschk 2015年

Answers:


267

使用C ++ 17shared_ptr可用于管理动态分配的数组。shared_ptr在这种情况下,模板参数必须为T[N]T[]。所以你可以写

shared_ptr<int[]> sp(new int[10]);

从n4659开始,[util.smartptr.shared.const]

  template<class Y> explicit shared_ptr(Y* p);

要求: Y应为完整类型。表达式delete[] pwhen T为数组类型,或者delete pwhen T为非数组类型,应具有明确定义的行为,并且不得引发异常。
...
备注:T是数组类型时,该构造函数不得参与重载解析,除非该表达式delete[] p格式正确,并且TU[N]Y(*)[N]可以转换为T*,或者TU[]Y(*)[]可以转换为T*。...

为了支持这一点,element_type现在将成员类型定义为

using element_type = remove_extent_t<T>;

数组元素可以使用 operator[]

  element_type& operator[](ptrdiff_t i) const;

要求: get() != 0 && i >= 0。如果TU[N]i < N。...
备注:T不是数组类型时,不确定是否声明此成员函数。如果声明了它,则不明确其返回类型是什么,除了函数的声明(尽管不一定是定义)必须格式正确。


之前C ++ 17shared_ptr可以被用来管理动态分配数组。默认情况下,当不再有对它的引用时,shared_ptr将调用delete该对象。但是,当您使用进行分配时,new[]您需要调用delete[]而不是delete来释放资源。

为了正确使用shared_ptr数组,必须提供自定义删除器。

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

如下创建shared_ptr:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

现在在销毁托管对象时shared_ptr将正确调用delete[]

上面的自定义删除器可以替换为

  • std::default_delete数组类型的部分专业化

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • Lambda表达式

    std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

另外,除非您确实需要共享托管对象的所有权,否则a unique_ptr更适合于此任务,因为它对数组类型有部分专长。

std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]

C ++图书馆基础知识扩展引入的更改

库基础技术规范提供了上述C ++ 17之前版本的另一种替代方法,该标准经过扩充shared_ptr后可以在拥有对象数组的情况下即开即用。shared_ptr可以在N4082中找到有关此TS 的更改的当前草案。这些更改将通过std::experimental名称空间进行访问,并包含在<experimental/memory>标头中。支持shared_ptr数组的一些相关更改包括:

—成员类型的定义element_type更改

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

operator[]正在添加成员

 element_type& operator[](ptrdiff_t i) const noexcept;

—与unique_ptr数组的部分专业化不同,两者shared_ptr<T[]>shared_ptr<T[N]>都将是有效的,并且两者都将导致在delete[]对象的托管数组上被调用。

 template<class Y> explicit shared_ptr(Y* p);

要求Y应为完整类型。表达式delete[] pwhen T为数组类型,或者delete pwhen T为非数组类型,格式应正确,行为应明确,并且不得引发异常。如果TU[N]Y(*)[N]应转换为T*; 当TU[]Y(*)[]应转变成T*; 否则,Y*应转换为T*


9
+1,备注:还有Boost的shared-array
jogojapan 2012年

5
@ tshah06 shared_ptr::get返回指向托管对象的指针。因此,您可以将其用作sp.get()[0] = 1; ... sp.get()[9] = 10;
Praetorian 2012年

55
ALT:std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );另请参见en.cppreference.com/w/cpp/memory/default_delete
yohjp 2012年

2
@Jeremy如果在编译时知道大小,则无需为此编写类,std::shared_ptr<std::array<int,N>>就足够了。
Praetorian

13
为什么会unique_ptr得到部分专业化而shared_ptr没有呢?
亚当

28

您可能可以使用的可能更简单的替代方法是shared_ptr<vector<int>>


5
是的。或者向量是数组的超集-它具有相同的内存表示形式(加上元数据),但是可调整大小。实际上,在任何情况下都不需要数组但不能使用向量。
Timmmm 2014年

2
此处的区别是向量大小不再是静态的,对数据的访问将通过双重间接方式完成。如果性能不是关键问题,则可以这样做,否则共享阵列可能有其自己的原因。
Emilio Garavaglia 2015年

4
然后,您可以使用shared_ptr<array<int, 6>>
Timmmm 2015年

10
另一个区别是,它比原始数组稍大且速度较慢。通常,这并不是真正的问题,但是我们不要假装1 == 1.1。
安德鲁(Andrew)

2
在某些情况下,数组中的数据源意味着不明智地或不必要地转换为向量。例如从相机取景时。(或者,无论如何,这是我的理解)
Narfanator '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.