Search code examples
c++c++11language-lawyershared-ptr

Why do shared_ptr deleters have to be CopyConstructible?


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 the following:

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);

The standard requires in [util.smartptr.shared.const] type D to be CopyConstructible. Why is this needed? If shared_ptr makes copies of d then which of these deleters might get called? Wouldn't it possible for a shared_ptr only to keep a single deleter around? What does it mean for a shared_ptr to own a deleter if d can be copied?

What is the rationale behind the CopyConstructible requirement?

PS: This requirement might complicate writing deleters for shared_ptr. unique_ptr seems to have much better requirements for its deleter.


Solution

  • 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 cleanup d(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 the new 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. :-)