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

在C ++ 11中std::shared_ptr有四个构造函数,可以传递typesD deleter对象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]typesD是CopyConstructible。 为什么这需要? 如果shared_ptr复制了d那么这些删除者中的哪一个可能被调用? shared_ptr只能保持一个删除器吗? 如果d可以被复制,那么对于shared_ptr 拥有一个删除器意味着什么?

CopyConstructible需求的基本原理是什么?

PS:这个要求可能会使shared_ptr写删除变得复杂。 unique_ptr似乎对删除器有更好的要求。

这个问题令人费解,我发邮件给Peter Dimov( boost::shared_ptr实现者,参与标准化std::shared_ptr

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

我的猜测是Deleter必须是CopyConstructible,实际上只是作为C ++ 03的遗迹而不存在移动语义。

你的猜测是正确的。 当指定shared_ptr ,右值引用还不存在。 现在我们应该可以不受约束地移动build设。

那时有一个微妙之处

 pi_ = new sp_counted_impl_pd<P, D>(p, d); 

抛出d必须保持完整,以便清理d(p)工作,但是我认为这不会是一个问题(尽pipe我没有真正尝试使实现移动友好)。
[…]
我认为这个定义没有问题,所以当new抛出时, d会保持原来的状态。

如果我们走得更远,并允许D有一个投掷移动构造函数,事情变得更加复杂。 但我们不会。 🙂

std::shared_ptrstd::unique_ptr中的删除器之间的区别在于, shared_ptr deleter是types擦除的,而在unique_ptr deletertypes是模板的一部分。

这里是Stephan T. Lavavej解释了types擦除如何导致std::function CopyConstructible需求。

至于这种指针types差异背后的原因,这个问题已经在几十次了,例如这里 。

STL说的一句话:

非常令人惊讶的“陷阱”我会说std::function需要CopyConstructible函数对象,这在STL中是不寻常的。

通常情况下,STL是懒惰的,它不需要事先说明:如果我有类似Tstd::list的东西, T不需要小于可比的; 只有当你调用成员函数list<T>::sort它确实需要小于可比的值。

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

这通常很酷 – 这意味着你只需要支付你所需要的东西,但是std::function是特殊的,因为types擦除,因为当你从某个可调用对象F构造std::function ,它需要生成所有你可能需要的东西从那个对象F因为它将会消除它的types。 它需要所有可能需要的操作,而不pipe它们是否被使用。

所以如果你从一个可调用的对象F构造一个std::function ,那么在编译时F是绝对需要的CopyConstructible。 即使F被移入std::function也是如此,所以即使你给了它一个r值,即使你从不在程序中的任何地方复制std::function s, F仍然需要被CopyConstructible。

你会得到一个编译器错误说 – 也许可怕,也许很好 – 取决于你得到什么。

它只是不能存储可移动的function对象。 这是一个devise限制,部分原因是std::function在r值引用之前可以追溯到boost / TR1,在某些意义上, std::function的接口永远无法修复。

替代品正在被调查,也许我们可以有一个不同的“可移动的function”,所以我们可能会得到某种types的擦除包装,可以存储可移动的function在未来,但std::function因为它站在C ++ 17现在不能这样做,所以请注意。

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