C++ Sharing ownership (std::shared_ptr)


Example

The class template std::shared_ptr defines a shared pointer that is able to share ownership of an object with other shared pointers. This contrasts to std::unique_ptr which represents exclusive ownership.

The sharing behavior is implemented through a technique known as reference counting, where the number of shared pointers that point to the object is stored alongside it. When this count reaches zero, either through the destruction or reassignment of the last std::shared_ptr instance, the object is automatically destroyed.


// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);

To create multiple smart pointers that share the same object, we need to create another shared_ptr that aliases the first shared pointer. Here are 2 ways of doing it:

std::shared_ptr<Foo> secondShared(firstShared);  // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared;                      // 2nd way: Assigning

Either of the above ways makes secondShared a shared pointer that shares ownership of our instance of Foo with firstShared.

The smart pointer works just like a raw pointer. This means, you can use * to dereference them. The regular -> operator works as well:

secondShared->test(); // Calls Foo::test()

Finally, when the last aliased shared_ptr goes out of scope, the destructor of our Foo instance is called.

Warning: Constructing a shared_ptr might throw a bad_alloc exception when extra data for shared ownership semantics needs to be allocated. If the constructor is passed a regular pointer it assumes to own the object pointed to and calls the deleter if an exception is thrown. This means shared_ptr<T>(new T(args)) will not leak a T object if allocation of shared_ptr<T> fails. However, it is advisable to use make_shared<T>(args) or allocate_shared<T>(alloc, args), which enable the implementation to optimize the memory allocation.


Allocating Arrays([]) using shared_ptr

C++11C++17

Unfortunately, there is no direct way to allocate Arrays using make_shared<>.

It is possible to create arrays for shared_ptr<> using new and std::default_delete.

For example, to allocate an array of 10 integers, we can write the code as

shared_ptr<int> sh(new int[10], std::default_delete<int[]>());

Specifying std::default_delete is mandatory here to make sure that the allocated memory is correctly cleaned up using delete[].

If we know the size at compile time, we can do it this way:

template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
  std::shared_ptr<T> operator()const{
    auto r = std::make_shared<std::array<T,N>>();
    if (!r) return {};
    return {r.data(), r};
  }
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }

then make_shared_array<int[10]> returns a shared_ptr<int> pointing to 10 ints all default constructed.

C++17

With C++17, shared_ptr gained special support for array types. It is no longer necessary to specify the array-deleter explicitly, and the shared pointer can be dereferenced using the [] array index operator:

std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;

Shared pointers can point to a sub-object of the object it owns:

struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);

Both p2 and p1 own the object of type Foo, but p2 points to its int member x. This means that if p1 goes out of scope or is reassigned, the underlying Foo object will still be alive, ensuring that p2 does not dangle.


Important: A shared_ptr only knows about itself and all other shared_ptr that were created with the alias constructor. It does not know about any other pointers, including all other shared_ptrs created with a reference to the same Foo instance:

Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this

shared1.reset(); // this will delete foo, since shared1
                 // was the only shared_ptr that owned it

shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
                 // deleted already!!

Ownership Transfer of shared_ptr

By default, shared_ptr increments the reference count and doesn't transfer the ownership. However, it can be made to transfer the ownership using std::move:

shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1