在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_ptr
,d
拥有删除者意味着什么?
CopyConstructible要求背后的理由是什么?
PS:此要求可能会使shared_ptr
的写入删除器复杂化。 unique_ptr
似乎对其删除有更好的要求。
答案 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_ptr
和std::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
类似T
,T
则不需要低于可比性;只有当你调用成员函数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
作为删除器,但这听起来有点矫枉过正。