简体   繁体   中英

Shouldn't `std::shared_ptr` use `std::default_delete` by default?

std::default_delete can be specialized to allow std::unique_ptr s to painlessly manage types which have to be destroyed by calling some custom destroy-function instead of using delete p; .

There are basically two ways to make sure an object is managed by a std::shared_ptr in C++:

  1. Create it managed by a shared-pointer, using std::make_shared or std::allocate_shared . This is the preferred way, as it coalesces both memory-blocks needed (payload and reference-counts) into one. Though iff there are only std::weak_ptr s left, the need for the reference-counts will by necessity still pin down the memory for the payload too.

  2. Assign management to a shared-pointer afterwards, using a constructor or .reset() .

The second case, when not providing a custom deleter is interesting:

Specifically, it is defined to use its own deleter of unspecified type which uses delete [] p; or delete p; respectively, depending on the std::shared_ptr being instantiated for an Array or not.

Quote from n4659 (~C++17):

 template<class Y> explicit shared_ptr(Y* p); 

4 Requires: Y shall be a complete type. The expression delete[] p , when T is an array type, or delete p , when T is not an array type, shall have well-defined behavior, and shall not throw exceptions.
5 Effects: When T is not an Array type, constructs a shared_ptr object that owns the pointer p . Otherwise, constructs a shared_ptr that owns p and a deleter of an unspecified type that calls delete[] p . When T is not an array type, enables shared_from_this with p . If an exception is thrown, delete p is called when T is not an array type, delete[] p otherwise.
6 Postconditions: use_count() == 1 && get() == p .
[…]

 template<class Y> void reset(Y* p); 

3 Effects: Equivalent to shared_ptr(p).swap(*this) .

My questions are:

  1. Is there a, preferably good, reason that it is not specified to use std::default_delete instead?
  2. Would any valid (and potentially useful?) code be broken by that change?
  3. Is there already a proposal to do so?

Is there a, preferably good, reason that it is not specified to use std::default_delete instead?

Because it wouldn't do what you want. See, just because you can specialize something doesn't mean you can hijack it. The standard says ([namespace.std]):

A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.

The standard library requirement for std::default_delete<T>::operator()(T* ptr) 's behavior is that it "Calls delete on ptr ." So your specialization of it must do the same.

As such, there should be no difference between having shared_ptr perform delete ptr; and having shared_ptr invoke default_delete<T>{}(ptr) .

This is why unique_ptr takes a deleter type, rather than relying on you to specialize it.


From the comments:

The specialization deletes the object, in the only proper way.

But that's not what the requirement says. It says "Calls delete on ptr ." It does not say something more ambiguous like "ends the lifetime of the object pointed to by ptr " or "destroys the object referenced by ptr ". It gives explicit code that must happen.

And your specialization has to follow through.

If you remain unconvinced, the paper P0722R1 says this:

Note that the standard requires specializations of default_delete<T> to have the same effect as calling delete p; ,

So clearly, the authors agree that specializing default_delete is not a mechanism for adding your own behavior.

So the premise of your question is invalid.


However, let's pretend for a moment that your question were valid, that such a specialization would work. Valid or not, specializing default_delete to customize deleter behavior is not the intended method of doing so. If it were the intent, you wouldn't need a deleter object for unique_ptr at all. At most, you would just need a parameter that tells you what the pointer type is, which would default to T* .

So that's a good reason not to do this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM