How to: Create and Use shared_ptr instances
The shared_ptr
type is a smart pointer in the C++ standard library that is designed for scenarios in which more than one owner needs to manage the lifetime of an object. After you initialize a shared_ptr
you can copy it, pass it by value in function arguments, and assign it to other shared_ptr
instances. All the instances point to the same object, and share access to one "control block" that increments and decrements the reference count whenever a new shared_ptr
is added, goes out of scope, or is reset. When the reference count reaches zero, the control block deletes the memory resource and itself.
The following illustration shows several shared_ptr
instances that point to one memory location.
The first diagram shows a shared pointer, P1, that points to a MyClass instance as well as a control block with ref count = 1. The second diagram shows the addition of another shared pointer, P2, which also points to the MyClass instance and to the shared control block, which now has a ref count of 2.
Example setup
The examples that follow all assume that you've included the required headers and declared the required types, as shown here:
// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct MediaAsset
{
virtual ~MediaAsset() = default; // make it polymorphic
};
struct Song : public MediaAsset
{
std::wstring artist;
std::wstring title;
Song(const std::wstring& artist_, const std::wstring& title_) :
artist{ artist_ }, title{ title_ } {}
};
struct Photo : public MediaAsset
{
std::wstring date;
std::wstring location;
std::wstring subject;
Photo(
const std::wstring& date_,
const std::wstring& location_,
const std::wstring& subject_) :
date{ date_ }, location{ location_ }, subject{ subject_ } {}
};
using namespace std;
int main()
{
// The examples go here, in order:
// Example 1
// Example 2
// Example 3
// Example 4
// Example 6
}
Example 1
Whenever possible, use the make_shared
function to create a shared_ptr
when the memory resource is created for the first time. make_shared
is exception-safe. It uses the same call to allocate the memory for the control block and the resource, which reduces the construction overhead. If you don't use make_shared
, then you have to use an explicit new
expression to create the object before you pass it to the shared_ptr
constructor. The following example shows various ways to declare and initialize a shared_ptr
together with a new object.
// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");
// Ok, but slightly less efficient.
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));
// When initialization must be separate from declaration, e.g. class members,
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");
Example 2
The following example shows how to declare and initialize shared_ptr
instances that take on shared ownership of an object that was allocated by another shared_ptr
. Assume that sp2
is an initialized shared_ptr
.
//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);
//Initialize via assignment. Increments ref count.
auto sp4 = sp2;
//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);
// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);
Example 3
shared_ptr
is also helpful in C++ Standard Library containers when you're using algorithms that copy elements. You can wrap elements in a shared_ptr
, and then copy it into other containers with the understanding that the underlying memory is valid as long as you need it, and no longer. The following example shows how to use the remove_copy_if
algorithm on shared_ptr
instances in a vector.
vector<shared_ptr<Song>> v {
make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
make_shared<Song>(L"ThalĂa", L"Entre El Mar y Una Estrella")
};
vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s)
{
return s->artist.compare(L"Bob Dylan") == 0;
});
for (const auto& s : v2)
{
wcout << s->artist << L":" << s->title << endl;
}
Example 4
You can use dynamic_pointer_cast
, static_pointer_cast
, and const_pointer_cast
to cast a shared_ptr
. These functions resemble the dynamic_cast
, static_cast
, and const_cast
operators. The following example shows how to test the derived type of each element in a vector of shared_ptr
of base classes, and then copies the elements and display information about them.
vector<shared_ptr<MediaAsset>> assets {
make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};
vector<shared_ptr<MediaAsset>> photos;
copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
// Use dynamic_pointer_cast to test whether
// element is a shared_ptr<Photo>.
shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
return temp.get() != nullptr;
});
for (const auto& p : photos)
{
// We know that the photos vector contains only
// shared_ptr<Photo> objects, so use static_cast.
wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}
Example 5
You can pass a shared_ptr
to another function in the following ways:
Pass the
shared_ptr
by value. This invokes the copy constructor, increments the reference count, and makes the callee an owner. There's a small amount of overhead in this operation, which may be significant depending on how manyshared_ptr
objects you're passing. Use this option when the implied or explicit code contract between the caller and callee requires that the callee be an owner.Pass the
shared_ptr
by reference or const reference. In this case, the reference count isn't incremented, and the callee can access the pointer as long as the caller doesn't go out of scope. Or, the callee can decide to create ashared_ptr
based on the reference, and become a shared owner. Use this option when the caller has no knowledge of the callee, or when you must pass ashared_ptr
and want to avoid the copy operation for performance reasons.Pass the underlying pointer or a reference to the underlying object. This enables the callee to use the object, but it doesn't share ownership of the object with the caller's
shared_ptr
. Beware the case of the callee creating ashared_ptr
from the passed raw pointer because the callee'sshared_ptr
has an independent reference count from the caller'sshared_ptr
. When theshared_ptr
in the callee goes out of scope, it will delete the object, leaving the pointer in the caller's 'shared_ptr' pointing at released memory. When the caller'sshared_ptr
then goes out of scope, a double-free results. Only use this option when the contract between the caller and callee clearly specifies that the caller retains ownership of theshared_ptr
lifetime.When you're deciding how to pass a
shared_ptr
, determine whether the callee has to share ownership of the underlying resource. An "owner" is an object or function that can keep the underlying resource alive for as long as it needs it. If the caller has to guarantee that the callee can extend the life of the pointer beyond its (the function's) lifetime, use the first option. If you don't care whether the callee extends the lifetime, then pass by reference and let the callee copy it or not.If you have to give a helper function access to the underlying pointer, and you know that the helper function uses the pointer and return before the calling function returns, then that function doesn't have to share ownership of the underlying pointer. It just has to access the pointer within the lifetime of the caller's
shared_ptr
. In this case, it's safe to pass theshared_ptr
by reference, or pass the raw pointer or a reference to the underlying object. Passing this way provides a small performance benefit, and may also help you express your programming intent.Sometimes, for example in a
std::vector<shared_ptr<T>>
, you may have to pass eachshared_ptr
to a lambda expression body or named function object. If the lambda or function doesn't store the pointer, then pass theshared_ptr
by reference to avoid invoking the copy constructor for each element.
void use_shared_ptr_by_value(shared_ptr<int> sp);
void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);
void use_raw_pointer(int* p);
void use_reference(int& r);
void test() {
auto sp = make_shared<int>(5);
// Pass the shared_ptr by value.
// This invokes the copy constructor, increments the reference count, and makes the callee an owner.
use_shared_ptr_by_value(sp);
// Pass the shared_ptr by reference or const reference.
// In this case, the reference count isn't incremented.
use_shared_ptr_by_reference(sp);
use_shared_ptr_by_const_reference(sp);
// Pass the underlying pointer or a reference to the underlying object.
use_raw_pointer(sp.get());
use_reference(*sp);
// Pass the shared_ptr by value.
// This invokes the move constructor, which doesn't increment the reference count
// but in fact transfers ownership to the callee.
use_shared_ptr_by_value(move(sp));
}
Example 6
The following example shows how shared_ptr
overloads various comparison operators to enable pointer comparisons on the memory that is owned by the shared_ptr
instances.
// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");
// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);
shared_ptr<Song> p2(song2);
// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;
// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl;