你能让std::shared_ptr管理使用new T[]分配的数组吗?

210
你能让一个std::shared_ptr指向一个数组吗?例如,
std::shared_ptr<int> sp(new int[10]);

如果不行,那为什么呢?我已经意识到的一个原因是无法对std::shared_ptr进行递增/递减操作。因此,它不能像普通指向数组的指针一样使用。

2
FWIT,您也可以考虑只使用std::vector。您必须小心地使用引用来传递数组,以便不会复制它。访问数据的语法比shared_ptr更清晰,并且调整大小非常容易。如果您需要STL的所有好处,那么您就可以得到它们。 - Nicu Stiurca
6
如果数组的大小在编译时已确定,您可以考虑使用 std::array。它与原始数组几乎相同,但具有适用于大多数库组件的正确语义。特别是该类型的对象使用 delete 而不是 delete[] 销毁。而且,与vector不同,它直接将数据存储在对象中,因此不会额外分配内存。 - celtschk
2个回答

323

C++17 中,shared_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);

Requires: Y shall be a complete type. The expression delete[] p, when T is an array type, or delete p, when T is not an array type, shall have well-defined behavior, and shall not throw exceptions.
...
Remarks: When T is an array type, this constructor shall not participate in overload resolution unless the expression delete[] p is well-formed and either T is U[N] and Y(*)[N] is convertible to T*, or T is U[] and Y(*)[] is convertible to T*. ...

为了支持这一点,成员类型element_type现在被定义为:
using element_type = remove_extent_t<T>;

可以使用operator[]来访问数组元素。

  element_type& operator[](ptrdiff_t i) const;

Requires: get() != 0 && i >= 0. If T is U[N], i < N. ...
Remarks: When T is not an array type, it is unspecified whether this member function is declared. If it is declared, it is unspecified what its return type is, except that the declaration (although not necessarily the definition) of the function shall be well formed.

在C++17之前,不能使用shared_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[]
上述自定义删除器可以被替换为:
  • the std::default_delete partial specialization for array types

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
    
  • a lambda expression

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

另外,除非你实际需要共享托管对象的所有权,否则对于此任务来说 unique_ptr 更加适用,因为它有针对数组类型的部分特化。

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

C++ 扩展对库基础的更改

C++17 之前另一种解决方法是由库基础技术规范提供的,这个规范增强了 shared_ptr 以使其可以直接管理对象数组。目前计划在该技术规范中更改 shared_ptr 的草案可以在N4082中找到。这些更改将通过 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);

Requires: Y shall be a complete type. The expression delete[] p, when T is an array type, or delete p, when T is not an array type, shall be well-formed, shall have well defined behavior, and shall not throw exceptions. When T is U[N], Y(*)[N] shall be convertible to T*; when T is U[], Y(*)[] shall be convertible to T*; otherwise, Y* shall be convertible to T*.


10
+1,备注:Boost库中还有shared-array(http://www.boost.org/doc/libs/1_51_0/libs/smart_ptr/shared_array.htm). - jogojapan
5
@tshah06 shared_ptr::get 返回托管对象的指针。因此,您可以将其用作 sp.get()[0] = 1; ... sp.get()[9] = 10; - Praetorian
57
std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>()); 这行代码使用了 C++11 中的智能指针 std::shared_ptr 来管理一个长度为 10 的整型数组。其中 std::default_delete<int[]>() 是删除器,它告诉 shared_ptr 在不需要这个指针时该如何释放内存。注意,new int[10] 分配的是动态内存,因此必须使用 delete[] 操作符来释放它,而 std::default_delete<int[]> 就会把它作为参数传递给 delete[] 操作符。 - yohjp
2
@Jeremy 如果大小在编译时已知,则无需为此编写类,std::shared_ptr<std::array<int,N>> 应该就足够了。 - Praetorian
13
为什么 unique_ptr 有那个偏特化而 shared_ptr 没有? - Adam
显示剩余7条评论

32

你可以使用一个可能更容易的替代方案,即 shared_ptr<vector<int>>


6
是的。或者说,向量是数组的超集——它具有相同的内存表示(加上元数据),但可以调整大小。实际上并没有什么情况需要使用数组而不能使用向量。 - Timmmm
2
这里的区别在于向量大小不再是静态的,而且对数据的访问将通过双重间接性进行。如果性能不是关键问题,那么这样做是可行的,否则共享一个数组可能有其自身的原因。 - Emilio Garavaglia
7
你可以尝试使用 shared_ptr<array<int, 6>> - Timmmm
12
另一个区别是它比原始数组稍微大且慢一些。通常情况下这并不是什么问题,但我们也不能假装1等于1.1。 - Andrew
3
有些情况下,数组中的数据源意味着将其转换为向量是笨重或不必要的;例如从相机获取帧时。(或者,这是我的理解) - Narfanator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接