为什么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_ptr
和std::unique_ptr
中的删除器之间的区别在于, shared_ptr
deleter是types擦除的,而在unique_ptr
deletertypes是模板的一部分。
这里是Stephan T. Lavavej解释了types擦除如何导致std::function
CopyConstructible需求。
至于这种指针types差异背后的原因,这个问题已经在几十次了,例如这里 。
STL说的一句话:
非常令人惊讶的“陷阱”我会说
std::function
需要CopyConstructible函数对象,这在STL中是不寻常的。通常情况下,STL是懒惰的,它不需要事先说明:如果我有类似
T
的std::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
作为删除,但这听起来有点矫枉过正。