85

The cplusplus.com shared_ptr page calls out a distinction between an empty std::shared_ptr and a null shared_ptr. The cppreference.com page doesn't explicitly call out the distinction, but uses both "empty" and comparison to nullptr in its description of std::shared_ptr behavior.

Is there a difference between an empty and a null shared_ptr? Is there any use case for such mixed-behavior pointers? Does a non-empty null shared_ptr even make sense? Would there ever be a case in normal usage (i.e. if you didn't explicitly construct one) where you could end up with an empty-but-non-null shared_ptr?

And do any of these answers change if you're using the Boost version instead of the C++11 version?

Niall
  • 30,036
  • 10
  • 99
  • 142
R.M.
  • 3,461
  • 1
  • 21
  • 41

2 Answers2

87

It's a weird corner of shared_ptr behavior. It has a constructor that allows you to make a shared_ptr that owns something and points to something else:

template< class Y > 
shared_ptr( const shared_ptr<Y>& r, T *ptr );

The shared_ptr constructed using this constructor shares ownership with r, but points to whatever ptr points to (i.e., calling get() or operator->() will return ptr). This is handy for cases where ptr points to a subobject (e.g., a data member) of the object owned by r.

The page you linked calls a shared_ptr that owns nothing empty, and a shared_ptr that points to nothing (i.e., whose get() == nullptr) null. (Empty is used in this sense by the standard; null isn't.) You can construct a null-but-not-empty shared_ptr, but it won't be very useful. An empty-but-not-null shared_ptr is essentially a non-owning pointer, which can be used to do some weird things like passing a pointer to something allocated on the stack to a function expecting a shared_ptr (but I'd suggest punching whoever put shared_ptr inside the API first).

boost::shared_ptr also has this constructor, which they call the aliasing constructor.

Community
  • 1
  • 1
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 8
    Worth noting: **C++11 § 20.7.2.2.1 (p16)** "Note: This constructor allows creation of an empty `shared_ptr` instance with a non-NULL stored pointer." Also worth mentioning the preceding note (p15), "To avoid the possibility of a dangling pointer, the user of this constructor must ensure that `p` remains valid at least until the ownership group of `r` is destroyed." A rarely used construction indeed. – WhozCraig Sep 18 '14 at 19:31
  • @Cubbi A `shared_ptr` whose `get()` returns `nullptr` *does* compare equal to `nullptr` regardless of whether it owns anything. – T.C. Sep 18 '14 at 20:55
  • 3
    A null-but-nonempty `shared_ptr`s *can* be useful, to make sure some function gets executed once all owning pointers run out of scope (even in case of an exception!). Not sure, whether there is now a special class for this. – coldfix Sep 18 '14 at 20:58
  • @coldfix What can a null-but-nonempty `shared_ptr` do that a non-null-and-nonempty `shared_ptr` can't? – T.C. Sep 18 '14 at 21:09
  • @T.C. you can use it to call a function with a null pointer as first argument:P. Also if you only care about some function (or lambda) getting executed at specific time and don't know any object to pass to it, you would use null to make that explicit. I admit, using a dedicated class without pointer functionality would be even more explicit and better, but also more work for small projects;). See [Using shared_ptr to execute code on block exit](http://www.boost.org/doc/libs/1_56_0/libs/smart_ptr/sp_techniques.html#on_block_exit) – coldfix Sep 18 '14 at 21:41
  • @coldfix It appears to me that while you can create a null-but-nonempty shared pointer, it must piggy back on a nonnull nonempty to get its reference count. Or does passing a non-`nullptr` null to `shared_ptr` create a reference counting block? – Yakk - Adam Nevraumont Sep 19 '14 at 13:27
  • @Yakk It seems that passing along a custom deleter (or a non-nullptr null) is sufficient to cause a reference counting block to be created, given the way the spec is written. – T.C. Sep 19 '14 at 13:30
  • 2
    The aliasing constructor originates from Bloomberg and was proposed for the standard before it was implemented in Boost (see [N1851](http://www.open-std.org/jtc1/SC22/wg21/docs/papers/2005/n1851.pdf)). I prefer the standard's term "shares ownership with `r`" to the phrasing "owns whatever `r` owns" – Jonathan Wakely Jan 15 '15 at 00:27
  • @JonathanWakely tweaked. I'm really just citing boost for the name "aliasing constructor", since it's not found in the standard. – T.C. Jan 15 '15 at 02:27
12

Is there a difference between an empty and a null shared_ptr?

Empty shared_ptr doesn't have control block and its use count considered to be 0. Copy of empty shared_ptr is another empty shared_ptr. They are both separate shared_ptrs that doesn't share common control block because they don't have it. Empty shared_ptr can be constructed with default constructor or with constructor that takes nullptr.

Non-empty null shared_ptr has control block that can be shared with other shared_ptrs. Copy of non-empty null shared_ptr is shared_ptr that shares the same control block as original shared_ptr so use count is not 0. It can be said that all copies of shared_ptr share the same nullptr. Non-empty null shared_ptr can be constructed with null pointer of object's type (not nullptr)

Here is example:

#include <iostream>
#include <memory>

int main()
{
    std::cout << "std::shared_ptr<int> ptr1:" << std::endl;
    {
        std::shared_ptr<int> ptr1;
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    std::cout << "std::shared_ptr<int> ptr1(nullptr):" << std::endl;
    {
        std::shared_ptr<int> ptr1(nullptr);
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    std::cout << "std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))" << std::endl;
    {
        std::shared_ptr<int> ptr1(static_cast<int*>(nullptr));
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    return 0;
}

It outputs:

std::shared_ptr<int> ptr1:
    use count before copying ptr: 0
    use count  after copying ptr: 0
    ptr1 is null

std::shared_ptr<int> ptr1(nullptr):
    use count before copying ptr: 0
    use count  after copying ptr: 0
    ptr1 is null

std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))
    use count before copying ptr: 1
    use count  after copying ptr: 2
    ptr1 is null

http://coliru.stacked-crooked.com/a/54f59730905ed2ff

anton_rh
  • 8,226
  • 7
  • 45
  • 73
  • 1
    I think this better answers why we have to check for null in custom deleter of shared_ptr. [Does it make sense to check for nullptr in custom deleter of shared_ptr?](https://stackoverflow.com/questions/42962515/does-it-make-sense-to-check-for-nullptr-in-custom-deleter-of-shared-ptr/42962962) – David Lee Jul 10 '17 at 12:47