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++:
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.
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 expressiondelete[] p
, whenT
is an array type, ordelete p
, whenT
is not an array type, shall have well-defined behavior, and shall not throw exceptions.
5 Effects: WhenT
is not an Array type, constructs ashared_ptr
object that owns the pointerp
. Otherwise, constructs ashared_ptr
that ownsp
and a deleter of an unspecified type that callsdelete[] p
. WhenT
is not an array type, enablesshared_from_this
withp
. If an exception is thrown,delete p
is called whenT
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:
std::default_delete
instead?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 callingdelete 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.