【问题标题】:shared_ptr to an array : should it be used?shared_ptr 到一个数组:应该使用它吗?
【发布时间】:2012-10-15 05:55:01
【问题描述】:

只是一个关于shared_ptr的小查询。

使用shared_ptr 指向数组是一种好习惯吗?例如,

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

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

【问题讨论】:

  • FWIT,你也可以考虑只使用std::vector。您必须小心使用引用传递数组,以免复制它。访问数据的语法比 shared_ptr 更简洁,并且调整它的大小非常容易。如果你想要的话,你会得到所有的 STL 优点。
  • 如果数组的大小是在编译时确定的,你也可以考虑使用std::array。它几乎与原始数组相同,但具有适用于大多数库组件的适当语义。尤其是那种类型的对象会被delete 销毁,而不是delete[]。并且与vector 不同,它直接将数据存储在对象中,因此您不会获得额外的分配。

标签: c++ c++11 shared-ptr


【解决方案1】:

使用 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);

要求: Y 应该是一个完整的类型。表达式delete[] p,当T 是数组类型时,或delete p,当T 不是数组类型时,应具有明确定义的行为,并且不应抛出异常。
...
备注: 当T 是数组类型时,此构造函数不应参与重载决议,除非表达式delete[] p 是格式正确的并且T 是@987654344 @ 和 Y(*)[N] 可转换为 T*,或者 T 是 U[] 和 Y(*)[] 可转换为 T*。 ...

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

using element_type = remove_extent_t<T>;

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

  element_type& operator[](ptrdiff_t i) const;

需要: get() != 0 &amp;&amp; i &gt;= 0。如果T 是U[N],i &lt; N。 ...
备注:当T不是数组类型时,是否声明该成员函数是未指定的。如果是声明的,它的返回类型是什么是未指定的,只是函数的声明(虽然不一定是定义)应该是格式良好的。


在 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[]。

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

  • 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; });
    

此外,除非您确实需要托管对象的共享权限,否则 unique_ptr 更适合此任务,因为它对数组类型具有部分特化。

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

C++ Extensions for Library Fundamentals 引入的变化

Library Fundamentals Technical Specification 提供了上面列出的另一个 C++17 之前的替代方案,它对shared_ptr 进行了扩充,使其能够在拥有对象数组的情况下开箱即用。可以在N4082 中找到为此 TS 计划的 shared_ptr 更改的当前草案。这些更改可通过std::experimental 命名空间访问,并包含在&lt;experimental/memory&gt; 标头中。支持数组的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&lt;T[]&gt; 和 shared_ptr&lt;T[N]&gt; 都是有效的,并且都会导致在托管对象数组上调用 delete[]。

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

要求:Y 应该是一个完整的类型。表达式delete[] p,当T 是数组类型时,或delete p,当T 不是数组类型时,应具有良好的格式,应具有良好定义的行为,并且不应抛出异常。当T 为U[N] 时,Y(*)[N] 应可转换为T*;当T 为U[] 时,Y(*)[] 应可转换为T*;否则,Y* 应可转换为 T*。

【讨论】:

  • +1,备注:还有Boost的shared-array。
  • @tshah06 shared_ptr::get 返回一个指向托管对象的指针。所以你可以把它当作sp.get()[0] = 1; ... sp.get()[9] = 10;
  • ALT:std::shared_ptr&lt;int&gt; sp( new int[10], std::default_delete&lt;int[]&gt;() ); 另见en.cppreference.com/w/cpp/memory/default_delete
  • @Jeremy 如果在编译时知道大小,则无需为此编写类,std::shared_ptr&lt;std::array&lt;int,N&gt;&gt; 就足够了。
  • 为什么unique_ptr 获得部分专业化而shared_ptr 没有?
【解决方案2】:

shared_ptr&lt;vector&lt;int&gt;&gt; 是您可能使用的更简单的替代方法。

【讨论】:

  • 是的。或者向量是数组的超集——它具有相同的内存表示(加上元数据),但可以调整大小。没有真正需要数组但不能使用向量的情况。
  • 这里的区别在于向量大小不再是静态的,并且对数据的访问将通过双重间接完成。如果性能不是关键问题,这是可行的,否则共享数组可能有其自身的原因。
  • 那么你大概可以使用shared_ptr&lt;array&lt;int, 6&gt;&gt;。
  • 另一个区别是它比原始数组略更大更慢。通常不是真正的问题,但我们不要假装 1 == 1.1。
  • 有些情况下,数组中数据的来源意味着转换成向量不方便或者没必要;例如从相机获取帧时。 (或者,这是我的理解,无论如何)
猜你喜欢
  • 2011-09-13
  • 1970-01-01
  • 2012-06-12
  • 1970-01-01
  • 2011-12-03
  • 1970-01-01
  • 2018-04-14
  • 2012-12-10
相关资源
最近更新 更多