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
dand a deleter of typeDinitialized 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_ptrobject that owns the objectpand the deleterd. WhenTis not an array type, the first and second constructors enableshared_from_thiswithp. The second and fourth constructors shall use a copy ofato 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
*thisis empty or shares ownership with anothershared_ptrinstance (use_count() > 1), there are no side effects.- Otherwise, if
*thisowns an objectpand a deleterd,d(p)is called.- Otherwise,
*thisowns a pointerp, anddelete pis 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_ptris invoked even if the managed pointer is null.
(Please use documentation!)