为什么 shared_ptr 删除器必须是 CopyConstructible?
Why do shared_ptr deleters have to be CopyConstructible?
在 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
不可能只保留一个删除器吗?如果 d
可以被复制,shared_ptr
到 拥有 删除器意味着什么?
CopyConstructible 要求背后的基本原理是什么?
PS:此要求可能会使 shared_ptr
的删除程序复杂化。 unique_ptr
似乎对其删除器有更好的要求。
因为 shared_ptr
意味着要被复制,而这些复制中的任何一个都可能必须删除该对象,所以它们都必须有权访问删除器。只保留一个删除器需要重新计算删除器本身。如果您真的希望发生这种情况,您可以使用嵌套的 std::shared_ptr
作为删除器,但这听起来有点矫枉过正。
这个问题非常令人困惑,以至于我给 Peter Dimov(boost::shared_ptr
的实施者并参与 std::shared_ptr
的标准化)发了电子邮件
以下是他所说的要点(经他许可转载):
My guess is that the Deleter had to be CopyConstructible really only as a
relic of C++03 where move semantics didn’t exist.
你猜对了。当 shared_ptr
被指定为右值引用时
还不存在。现在我们应该能够满足要求
nothrow move-constructible.
其中有一个微妙之处
pi_ = new sp_counted_impl_pd<P, D>(p, d);
抛出,d
必须完好无损才能使清理 d(p)
正常工作,但我
认为这不会是一个问题(虽然我实际上并没有
试图使实施移动友好)。
[...]
我认为不会有问题
定义它的实现,以便当 new
抛出时, d
将被保留
处于原始状态。
如果我们更进一步,允许 D
有一个投掷构造函数,事情就会变得
更复杂。但我们不会。 :-)
std::shared_ptr
和 std::unique_ptr
中删除器的区别在于 shared_ptr
删除器是类型擦除的,而 unique_ptr
中删除器类型是模板的一部分。
Here is Stephan T. Lavavej explaining 类型擦除如何导致 std::function
中的 CopyConstructible 要求。
至于这种指针类型差异背后的原因,已在 SO 上多次解决,例如.
引述什么 S.T.L。说:
Very surprising "gotcha" I would say is that the std::function
requires CopyConstructible function objects, and this is kind of unusual in the STL.
Usually the STL is lazy in the sense that it doesn't need things up front: if I have something like a std::list
of type T
, T
does not need to be less-than-comparable; only if you call the member function list<T>::sort
then it actually does need to be less-than-comparable.
The core language rule that powers this is that the definitions of member functions of a class template are not instantiated until they're actually needed and the bodies don't exist in some sense until you actually call it.
This is usually cool - this means you only pay for what you need, but std::function
is special because of type erasure, because when you construct the std::function
from some callable object F
it needs to generate everything you could ever need from that object F
because it's going to erase its type. It requires all the operations that it could possibly ever need regardless of if they're used.
So if you construct a std::function
from some callable object F
, F
is absolutely required at compile-time to be CopyConstructible. This is true even though F
will be moved into the std::function
, so even if you give it an r-value and even if you never copy std::function
s anywhere in your program, F
is still required to be CopyConstructible.
You'll get a compiler error saying so - maybe horrible, maybe nice - depending on what you get.
It just cannot store movable only function objects. This is a design limitation caused in part by the fact that std::function
dates back to boost/TR1, before r-value references, and in some sense it can never be fixed with std::function
's interface as it stands.
Alternatives are being investigated, maybe we can have a different "movable function", so we will probably get some sort of type-erased wrapper that can store movable only function in the future, but std::function
as it stands in c++17 right now cannot do that, so just be aware.
在 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
不可能只保留一个删除器吗?如果 d
可以被复制,shared_ptr
到 拥有 删除器意味着什么?
CopyConstructible 要求背后的基本原理是什么?
PS:此要求可能会使 shared_ptr
的删除程序复杂化。 unique_ptr
似乎对其删除器有更好的要求。
因为 shared_ptr
意味着要被复制,而这些复制中的任何一个都可能必须删除该对象,所以它们都必须有权访问删除器。只保留一个删除器需要重新计算删除器本身。如果您真的希望发生这种情况,您可以使用嵌套的 std::shared_ptr
作为删除器,但这听起来有点矫枉过正。
这个问题非常令人困惑,以至于我给 Peter Dimov(boost::shared_ptr
的实施者并参与 std::shared_ptr
的标准化)发了电子邮件
以下是他所说的要点(经他许可转载):
My guess is that the Deleter had to be CopyConstructible really only as a relic of C++03 where move semantics didn’t exist.
你猜对了。当
shared_ptr
被指定为右值引用时 还不存在。现在我们应该能够满足要求 nothrow move-constructible.其中有一个微妙之处
pi_ = new sp_counted_impl_pd<P, D>(p, d);
抛出,
d
必须完好无损才能使清理d(p)
正常工作,但我 认为这不会是一个问题(虽然我实际上并没有 试图使实施移动友好)。
[...]
我认为不会有问题 定义它的实现,以便当new
抛出时,d
将被保留 处于原始状态。如果我们更进一步,允许
D
有一个投掷构造函数,事情就会变得 更复杂。但我们不会。 :-)
std::shared_ptr
和 std::unique_ptr
中删除器的区别在于 shared_ptr
删除器是类型擦除的,而 unique_ptr
中删除器类型是模板的一部分。
Here is Stephan T. Lavavej explaining 类型擦除如何导致 std::function
中的 CopyConstructible 要求。
至于这种指针类型差异背后的原因,已在 SO 上多次解决,例如
引述什么 S.T.L。说:
Very surprising "gotcha" I would say is that the
std::function
requires CopyConstructible function objects, and this is kind of unusual in the STL.Usually the STL is lazy in the sense that it doesn't need things up front: if I have something like a
std::list
of typeT
,T
does not need to be less-than-comparable; only if you call the member functionlist<T>::sort
then it actually does need to be less-than-comparable.The core language rule that powers this is that the definitions of member functions of a class template are not instantiated until they're actually needed and the bodies don't exist in some sense until you actually call it.
This is usually cool - this means you only pay for what you need, but
std::function
is special because of type erasure, because when you construct thestd::function
from some callable objectF
it needs to generate everything you could ever need from that objectF
because it's going to erase its type. It requires all the operations that it could possibly ever need regardless of if they're used.So if you construct a
std::function
from some callable objectF
,F
is absolutely required at compile-time to be CopyConstructible. This is true even thoughF
will be moved into thestd::function
, so even if you give it an r-value and even if you never copystd::function
s anywhere in your program,F
is still required to be CopyConstructible.You'll get a compiler error saying so - maybe horrible, maybe nice - depending on what you get.
It just cannot store movable only function objects. This is a design limitation caused in part by the fact that
std::function
dates back to boost/TR1, before r-value references, and in some sense it can never be fixed withstd::function
's interface as it stands.Alternatives are being investigated, maybe we can have a different "movable function", so we will probably get some sort of type-erased wrapper that can store movable only function in the future, but
std::function
as it stands in c++17 right now cannot do that, so just be aware.