See this example :
#include
#include
class Foo {
public:
Foo() { std::cout <
tl;dr: Yes, it's intended.
This is pretty subtle.
A shared_ptr can be in two states:
get()
may return nullptr
(although some ctors exist which change this postcondition)p
; get()
returns p
.Constructing a shared_ptr
with a null pointer actually leads to it being not-empty! get()
returning p
means get()
returning nullptr
, but that doesn't make it empty.
Since the default deleter just does delete p
, and delete nullptr
is a no-op, this doesn't usually matter. But, as you have seen, you can observe this difference if you provide your own deleter.
I don't know exactly why this is. On the one hand I can see a case for preventing a deleter from being invoked in the nullptr case because one generally considers a shared_ptr(nullptr)
to be "empty" (even though it technically is not); on the other hand, I can see a case for letting the deleter make this decision (with the accompanying overhead of a branch) if it wants to.
You're right to include a check for null here.
Some legalese from [util.smartptr.shared.const]
:
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);
9) Requires: Construction of
d
and a deleter of typeD
initialized withstd::move(d)
shall not throw exceptions. The expressiond(p)
shall have well-defined behavior and shall not throw exceptions. A shall satisfy the Cpp17Allocator requirements (Table 34).10) Effects: Constructs a
shared_ptr
object that owns the objectp
and the deleterd
. WhenT
is not an array type, the first and second constructors enableshared_from_this
withp
. The second and fourth constructors shall use a copy ofa
to allocate memory for internal use. If an exception is thrown,d(p)
is called.11) Ensures:
use_count() == 1 && get() == p
.
(Notice that there is no exemption for the case that !p
.)
And from [util.smartptr.shared.dest]
:
~shared_ptr();
1) Effects:
- If
*this
is empty or shares ownership with anothershared_ptr
instance (use_count() > 1
), there are no side effects.- Otherwise, if
*this
owns an objectp
and a deleterd
,d(p)
is called.- Otherwise,
*this
owns a pointerp
, anddelete p
is called.
Sidenote: I think the confusion between the phrases "owns an object" and "owns a pointer" in the above passages is an editorial problem.
We can also see this documented on cppreference.com's ~shared_ptr article:
Unlike
std::unique_ptr
, the deleter ofstd::shared_ptr
is invoked even if the managed pointer is null.
(Please use documentation!)