What is the difference between an empty and a null std::shared_ptr in C++?

C++C++11Shared Ptr

C++ Problem Overview


The http://www.cplusplus.com/reference/memory/shared_ptr/">cplusplus.com shared_ptr page calls out a distinction between an empty std::shared_ptr and a null shared_ptr. The http://en.cppreference.com/w/cpp/memory/shared_ptr">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?

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionR.M.View Question on Stackoverflow
Solution 1 - C++T.C.View Answer on Stackoverflow
Solution 2 - C++anton_rhView Answer on Stackoverflow