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
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.
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_ptr
s 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