Why do shared_ptr deleters have to be CopyConstructible?

后端 未结 3 1401
盖世英雄少女心
盖世英雄少女心 2021-02-01 14:09

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

相关标签:
3条回答
  • 2021-02-01 14:41

    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. :-)

    0 讨论(0)
  • 2021-02-01 14:54

    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 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::functions 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.

    0 讨论(0)
  • 2021-02-01 14:55

    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.

    0 讨论(0)
提交回复
热议问题