std::shared_ptr calls non-default deleter on null pointer

后端 未结 1 899
暖寄归人
暖寄归人 2021-01-14 11:36

See this example :

#include 
#include 

class Foo {
public:
    Foo()  { std::cout <         


        
相关标签:
1条回答
  • 2021-01-14 12:20

    tl;dr: Yes, it's intended.


    This is pretty subtle.

    A shared_ptr can be in two states:

    • "empty": default-constructed or reset; holds no ownership; get() may return nullptr (although some ctors exist which change this postcondition)
    • not empty: holds ownership of a pointer 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 type D initialized with std::move(d) shall not throw exceptions. The expression d(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 object p and the deleter d. When T is not an array type, the first and second constructors enable shared_­from_­this with p. The second and fourth constructors shall use a copy of a 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 another shared_­ptr instance (use_­count() > 1), there are no side effects.
    • Otherwise, if *this owns an object p and a deleter d, d(p) is called.
    • Otherwise, *this owns a pointer p, and delete 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 of std::shared_ptr is invoked even if the managed pointer is null.

    (Please use documentation!)

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