为什么shared_ptr删除者必须是CopyConstructible?

时间:2016-04-20 12:45:35

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

在C ++ 11中std::shared_ptr有四个构造函数,可以传递类型为d的删除对象D。这些构造函数的签名如下:

template<class Y, class D> shared_ptr(Y * p, D d);
template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

[util.smartptr.shared.const]类型D中的标准要求为CopyConstructible。为什么需要这个?如果shared_ptr制作了d的副本,那么这些删除者中的哪一个可能被调用? shared_ptr只能保留一个删除器吗?如果可以复制shared_ptrd 拥有删除者意味着什么?

CopyConstructible要求背后的理由是什么?

PS:此要求可能会使shared_ptr的写入删除器复杂化。 unique_ptr似乎对其删除有更好的要求。

3 个答案:

答案 0 :(得分:21)

这个问题令人困惑,我通过电子邮件发送了Peter Dimov(boost::shared_ptr的实施者并参与std::shared_ptr的标准化)

这是他所说的要点(经许可转载):

  
    

我的猜测是,Deleter必须是CopyConstructible真的只是作为一个     C ++ 03的遗物,其中不存在移动语义。

  
     

你的猜测是正确的。当shared_ptr被指定为rvalue引用时   还不存在如今我们应该能够满足要求   nothrow move-constructible。

     

时,有一个微妙之处
pi_ = new sp_counted_impl_pd<P, D>(p, d);
     

抛出,d必须保持原样,以便清理d(p)起作用,但我   认为这不会是一个问题(虽然我实际上并没有   试图使实施变得友好。)   [...]
  我认为没有问题   实现以定义它,以便在new抛出时,d将被保留   处于原始状态。

     

如果我们进一步允许D有一个投掷移动构造函数,事情就会发生   更复杂。但我们不会。 :-)

答案 1 :(得分:2)

std::shared_ptrstd::unique_ptr中的删除者之间的区别在于shared_ptr删除器是类型删除的,而unique_ptr删除器类型是模板的一部分。

Here is Stephan T. Lavavej explaining类型擦除如何导致std::function中的CopyConstructible要求。

至于指针类型差异背后的原因,它已在SO上多次解决,例如: here

S.T.L.的引用表示:

  

非常令人惊讶&#34;陷阱&#34;我想说的是std::function需要CopyConstructible函数对象,这在STL中有点不寻常。

     

通常情况下,STL是懒惰的,因为它不需要事先预测:如果我的std::list类似TT则不需要低于可比性;只有当你调用成员函数list<T>::sort时,它实际上确实需要低于可比性。

     

支持这一功能的核心语言规则是,类模板的成员函数的定义在实际需要之前不会被实例化,并且在您实际调用之前,这些实体在某种意义上不存在。

     

这通常很酷 - 这意味着您只需为所需内容付费,但由于类型擦除,std::function是特殊的,因为当您从某个可调用对象std::function构造F时它需要从该对象F生成您可能需要的所有内容,因为它将删除其类型。它需要它可能需要的所有操作,无论它们是否被使用。

     

因此,如果从某个可调用对象std::function构造F,则在编译时绝对需要F为CopyConstructible。即使将F移动到std::function中也是如此,所以即使您给它一个r值,即使您从未在程序中的任何位置复制std::function,{{ 1}}仍然需要是CopyConstructible。

     

你会收到编译错误的说法 - 也许很可怕,也许很好 - 取决于你得到的东西。

     

它只能存储仅可移动的功能对象。这是一个设计限制,部分原因是F在r值引用之前可以追溯到boost / TR1,在某种意义上它永远无法用std::function来修复现在的界面。

     

正在研究替代方案,也许我们可以有一个不同的可移动功能&#34;,所以我们可能会得到某种类型擦除的包装器,可以在将来存储仅可移动的功能,但是{{1正如它在c ++ 17中所说的那样现在不能这样做,所以请注意。

答案 2 :(得分:-2)

由于要复制shared_ptr,并且这些副本中的任何一个都必须删除该对象,因此它们必须都可以访问删除器。只保留一个删除器就需要重新计算删除器本身。如果你真的想要这样做,你可以使用嵌套的std::shared_ptr作为删除器,但这听起来有点矫枉过正。

相关问题