In C++11 std::shared_ptr
has four constructors which can be passed deleter objects d
of type D
. The signatures of these constructors are t
This question was perplexing enough that I emailed Peter Dimov (implementer of boost::shared_ptr
and involved in standardization of std::shared_ptr
)
Here's the gist of what he said (reprinted with his permission):
My guess is that the Deleter had to be CopyConstructible really only as a relic of C++03 where move semantics didn’t exist.
Your guess is correct. When
shared_ptr
was specified rvalue references didn't exist yet. Nowadays we should be able to get by with requiring nothrow move-constructible.There is one subtlety in that when
pi_ = new sp_counted_impl_pd<P, D>(p, d);
throws,
d
must be left intact for the cleanupd(p)
to work, but I think that this would not be a problem (although I haven't actually tried to make the implementation move-friendly).
[...]
I think that there will be no problem for the implementation to define it so that when thenew
throws,d
will be left in its original state.If we go further and allow
D
to have a throwing move constructor, things get more complicated. But we won't. :-)
The difference between deleters in std::shared_ptr
and std::unique_ptr
is that shared_ptr
deleter is type-erased, while in unique_ptr
deleter type is part of the template.
Here is Stephan T. Lavavej explaining how type erasure leads to CopyConstructible requirement in std::function
.
As for the reason behind this difference in pointer types, it has been addressed on SO several times, e.g. here.
A quote of what S.T.L. said:
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.
Because shared_ptr
are meant to be copied, and any of those copy could have to delete the object, so they must all have access to a deleter. Keeping only one deleter would require, well, refcounting the deleter itself. If you really want that to happen, you could use a nested std::shared_ptr
as the deleter, but that sounds a bit overkill.