Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Shared Ownership Smart Pointers
Learn modern memory management with std::shared_ptr.
std::shared_ptr
Unlike std::unique_ptr, which is designed to singly own and manage a resource, std::shared_ptr is meant to solve the case where multiple smart pointers need to co-own a resource.
This means it's fine to have multiple std::shared_ptr pointing to the same resource. Internally, std::shared_ptr keeps track of how many std::shared_ptr are sharing the resource. As long as at least one std::shared_ptr is pointing to the resource, the resource won't be deallocated, even if individual std::shared_ptr are destroyed. As soon as the last std::shared_ptr managing the resource goes out of scope (or is reassigned to point at something else), the resource will be deallocated.
Like std::unique_ptr, std::shared_ptr lives in the
#include <iostream>
#include <memory> // for std::shared_ptr
class NetworkConnection
{
public:
NetworkConnection() { std::cout << "Connection established\n"; }
~NetworkConnection() { std::cout << "Connection closed\n"; }
};
int main()
{
// allocate a NetworkConnection object and have it owned by std::shared_ptr
NetworkConnection* conn{new NetworkConnection};
std::shared_ptr<NetworkConnection> ptr1{conn};
{
std::shared_ptr<NetworkConnection> ptr2{ptr1}; // make another std::shared_ptr pointing to the same thing
std::cout << "Destroying one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Destroying another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated NetworkConnection is destroyed
This prints:
Connection established
Destroying one shared pointer
Destroying another shared pointer
Connection closed
In the above code, we create a dynamic NetworkConnection object and set a std::shared_ptr named ptr1 to manage it. Inside the nested block, we use the copy constructor to create a second std::shared_ptr (ptr2) that points to the same NetworkConnection. When ptr2 goes out of scope, the NetworkConnection isn't deallocated because ptr1 is still pointing at the NetworkConnection. When ptr1 goes out of scope, ptr1 notices there are no more std::shared_ptr managing the NetworkConnection, so it deallocates the NetworkConnection.
Note that we created a second shared pointer from the first shared pointer. This is important. Consider the following similar program:
#include <iostream>
#include <memory> // for std::shared_ptr
class NetworkConnection
{
public:
NetworkConnection() { std::cout << "Connection established\n"; }
~NetworkConnection() { std::cout << "Connection closed\n"; }
};
int main()
{
NetworkConnection* conn{new NetworkConnection};
std::shared_ptr<NetworkConnection> ptr1{conn};
{
std::shared_ptr<NetworkConnection> ptr2{conn}; // create ptr2 directly from conn (instead of ptr1)
std::cout << "Destroying one shared pointer\n";
} // ptr2 goes out of scope here, and the allocated NetworkConnection is destroyed
std::cout << "Destroying another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated NetworkConnection is destroyed again
This program prints:
Connection established
Destroying one shared pointer
Connection closed
Destroying another shared pointer
Connection closed
and then crashes (at least when tested).
The difference is that we created two std::shared_ptr independently from each other. As a consequence, even though they're both pointing to the same NetworkConnection, they aren't aware of each other. When ptr2 goes out of scope, it thinks it's the only owner of the NetworkConnection and deallocates it. When ptr1 later goes out of scope, it thinks the same thing and tries to delete the NetworkConnection again. Then bad things happen.
Fortunately, this is easily avoided: if you need more than one std::shared_ptr to a given resource, copy an existing std::shared_ptr.
Always make a copy of an existing std::shared_ptr if you need more than one std::shared_ptr pointing to the same resource.
Just like with std::unique_ptr, std::shared_ptr can be a null pointer, so check to make sure it's valid before using it.
std::make_shared
Much like std::make_unique() can be used to create a std::unique_ptr in C++14, std::make_shared() can (and should) be used to make a std::shared_ptr. std::make_shared() is available in C++11.
Here's our original example, using std::make_shared():
#include <iostream>
#include <memory> // for std::shared_ptr
class NetworkConnection
{
public:
NetworkConnection() { std::cout << "Connection established\n"; }
~NetworkConnection() { std::cout << "Connection closed\n"; }
};
int main()
{
// allocate a NetworkConnection object and have it owned by std::shared_ptr
auto ptr1{std::make_shared<NetworkConnection>()};
{
auto ptr2{ptr1}; // create ptr2 using copy of ptr1
std::cout << "Destroying one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Destroying another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated NetworkConnection is destroyed
The reasons for using std::make_shared() are the same as std::make_unique() -- std::make_shared() is simpler and safer (there's no way to create two independent std::shared_ptr pointing to the same resource but unaware of each other using this method). However, std::make_shared() is also more performant than not using it. The reasons for this lie in how std::shared_ptr keeps track of how many pointers are pointing at a given resource.
Digging into std::shared_ptr
Unlike std::unique_ptr, which uses a single pointer internally, std::shared_ptr uses two pointers internally. One pointer points at the resource being managed. The other points at a "control block", which is a dynamically allocated object that tracks a bunch of stuff, including how many std::shared_ptr are pointing at the resource. When a std::shared_ptr is created via a std::shared_ptr constructor, the memory for the managed object (which is usually passed in) and control block (which the constructor creates) are allocated separately. However, when using std::make_shared(), this can be optimized into a single memory allocation, which leads to better performance.
This also explains why independently creating two std::shared_ptr pointed to the same resource causes trouble. Each std::shared_ptr will have one pointer pointing at the resource. However, each std::shared_ptr will independently allocate its own control block, which will indicate that it's the only pointer owning that resource. Thus, when that std::shared_ptr goes out of scope, it will deallocate the resource, not realizing there are other std::shared_ptr also trying to manage that resource.
However, when a std::shared_ptr is cloned using copy assignment, the data in the control block can be appropriately updated to indicate that there are now additional std::shared_ptr co-managing the resource.
Shared pointers can be created from unique pointers
A std::unique_ptr can be converted into a std::shared_ptr via a special std::shared_ptr constructor that accepts a std::unique_ptr r-value. The contents of the std::unique_ptr will be moved to the std::shared_ptr.
However, std::shared_ptr cannot be safely converted to a std::unique_ptr. This means that if you're creating a function that's going to return a smart pointer, you're better off returning a std::unique_ptr and assigning it to a std::shared_ptr if and when that's appropriate.
The perils of std::shared_ptr
std::shared_ptr has some of the same challenges as std::unique_ptr -- if the std::shared_ptr isn't properly disposed of (either because it was dynamically allocated and never deleted, or it was part of an object that was dynamically allocated and never deleted) then the resource it's managing won't be deallocated either. With std::unique_ptr, you only have to worry about one smart pointer being properly disposed of. With std::shared_ptr, you have to worry about them all. If any of the std::shared_ptr managing a resource aren't properly destroyed, the resource won't be deallocated properly.
std::shared_ptr and arrays
In C++17 and earlier, std::shared_ptr doesn't have proper support for managing arrays and should not be used to manage a C-style array. As of C++20, std::shared_ptr does have support for arrays.
Summary
std::shared_ptr purpose: Designed for shared ownership where multiple smart pointers co-manage the same resource. Lives in the
Reference counting: std::shared_ptr internally tracks how many shared pointers point to the resource. Each copy increments the count; each destruction decrements it. The resource is freed when the count reaches zero.
Creating shared pointers: Always copy an existing std::shared_ptr to create additional pointers to the same resource. Creating independent std::shared_ptr instances from the same raw pointer causes each to maintain separate reference counts, leading to double deletion.
std::make_shared(): Preferred way to create std::shared_ptr (available in C++11). Simpler, safer, and more performant than manual construction because it allocates the object and control block in a single memory allocation.
Control block: std::shared_ptr uses two pointers internally: one to the managed object and one to a control block containing the reference count. When using std::make_shared(), both allocations are optimized into one.
Conversion from std::unique_ptr: A std::unique_ptr can be converted to std::shared_ptr via move semantics. The reverse conversion is not safe. Return std::unique_ptr from functions that create resources; callers can convert to std::shared_ptr if needed.
Disposal requirements: All std::shared_ptr managing a resource must be properly destroyed for the resource to be deallocated. If any std::shared_ptr is leaked (e.g., dynamically allocated and never deleted), the resource won't be freed.
Array support: In C++17 and earlier, std::shared_ptr doesn't properly support arrays. As of C++20, std::shared_ptr has array support, but prefer std::vector or std::array when possible.
Use std::shared_ptr for shared ownership scenarios. Prefer std::make_shared() for creation. Always copy existing std::shared_ptr instances rather than creating independent ones from raw pointers.
Shared Ownership Smart Pointers - Quiz
Test your understanding of the lesson.
Practice Exercises
std::shared_ptr Basics
Learn to use std::shared_ptr for shared ownership of dynamically allocated resources. Understand reference counting and when resources are freed.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!