std::shared_ptr
std::shared_ptr
For most cases, we want our objects to have a single owner. Therefore, std::unique_ptr
should be our preferred way to store pointers.
However, in some cases, we need our objects to have multiple owners. This is where std::shared_ptr
can help us.
Similar to the previous lesson, we’ll be using this file to demonstrate how shared pointers work:
#include <iostream>
#include <memory>
using namespace std;
class Character {
public:
string Name;
Character(string Name = "Frodo") :
Name { Name }
{
cout << "Creating " << Name << endl;
}
~Character() {
cout << "Deleting " << Name << endl;
}
};
int main() {
// ...
}
std::make_shared
We previously saw how the helper function std::make_unique
created a std::unique_ptr
for us. Predictably, we also have std::make_shared
to create a std::shared_ptr
:
#include <memory>
int main() {
auto FrodoPointer { std::make_shared<Character>() };
}
The std::make_shared
function creates an object of the type specified between the <
and >
tokens, and returns a std::shared_ptr
of that same type.
For example std::make_shared<Character>
will create a Character
in dynamic memory, and return a std::shared_ptr<Character>
that points to it.
Constructor arguments can be passed between the ()
:
#include <memory>
int main() {
auto FrodoPointer {
std::make_shared<Character>("Gandalf")
};
}
In most ways, shared pointers can be used in exactly the same way as unique pointers. We can dereference them using the *
and ->
operators, and they automatically delete the object when appropriate:
#include <iostream>
#include <memory>
using namespace std;
int main() {
auto FrodoPointer { make_shared<Character>() };
cout << (*FrodoPointer).Name << endl;
cout << FrodoPointer->Name << endl;
cout << "Main Function Ending" << endl;
}
Creating Frodo
Frodo
Frodo
Main Function Ending
Deleting Frodo
We can access the raw pointer using get
:
#include <iostream>
#include <memory>
using namespace std;
void LogName(const Character* Ptr) {
cout << Ptr->Name << endl;
}
int main() {
auto FrodoPointer { make_shared<Character>() };
LogName(FrodoPointer.get());
cout << "Main Function Ending" << endl;
}
Creating Frodo
Frodo
Main Function Ending
Deleting Frodo
We can also transfer ownership using std::move
:
#include <iostream>
#include <memory>
using namespace std;
void TakeOwnership(shared_ptr<Character> Ptr) {
cout << Ptr->Name << endl;
}
int main() {
auto FrodoPointer { make_shared<Character>() };
TakeOwnership(move(FrodoPointer));
cout << "Main Function Ending" << endl;
}
As before with unique pointers, note how Frodo is now deleted at the end of the TakeOwnership
function, rather than at the end of main
Creating Frodo
Frodo
Deleting Frodo
Main Function Ending
We can also reset
 :
#include <iostream>
#include <memory>
int main() {
using namespace std;
auto Pointer { make_shared<Character>() };
Pointer.reset();
cout << "Main Function Ending" << endl;
}
Creating Frodo
Deleting Frodo
Main Function Ending
If the pointer we called reset
on was the only shared pointer that owned the resource, the resource will be deleted. More information on how ownership can be shared is coming later in this lesson.
Reset can also be used to replace the managed object with a new one of the same type (or convertible to the same type):
int main() {
using namespace std;
auto Pointer { make_shared<Character>("Frodo") };
Pointer.reset(new Character {"Gandalf"});
cout << "Main Function Ending" << endl;
}
Creating Frodo
Creating Gandalf
Deleting Frodo
Main Function Ending
Deleting Gandalf
And finally, we still have access to swap
:
#include <iostream>
#include <memory>
int main() {
using namespace std;
auto Pointer1 { make_shared<Character>("Frodo") };
auto Pointer2 { make_shared<Character>("Gandalf") };
cout << "1: " << Pointer1->Name << endl;
cout << "2: " << Pointer2->Name << endl;
Pointer1.swap(Pointer2);
cout << "1: " << Pointer1->Name << endl;
cout << "2: " << Pointer2->Name << endl;
}
Creating Frodo
Creating Gandalf
1: Frodo
2: Gandalf
1: Gandalf
2: Frodo
Deleting Frodo
Deleting Gandalf
Unlike unique pointers, shared pointers do not have a release
 function.
The key difference between unique and shared pointers is that, predictably, shared pointers can be shared. This is done simply by creating copies of them:
auto Pointer1 { std::make_shared<Character>() };
auto Pointer2 { Pointer1 };
Most commonly, these copies are created by passing the shared pointer by value into a function, or setting it as a member variable on some object:
auto Pointer { make_shared<Character>() };
SomeFunction(Pointer);
SomeObject.SomeVariable = Pointer;
This is the key mechanism that allows a resource to have multiple owners. Every function or object that has a copy of the shared pointer is considered an owner.
The underlying resource is deleted only when all of its owners are deleted.
We can see how many owners a shared pointer has using the use_count
class function:
#include <iostream>
#include <memory>
using namespace std;
class Party {
public:
shared_ptr<Character> Member;
};
int main() {
auto Frodo { make_shared<Character>() };
auto Party1 { make_unique<Party>()};
auto Party2 { make_unique<Party>()};
cout << "Frodo has "
<< Frodo.use_count()
<< " owner (the main function)"
<< endl << endl;
cout << "Giving Party1 a copy" << endl;
Party1->Member = Frodo;
cout << "Frodo has "
<< Frodo.use_count()
<< " owners (main and Party1)"
<< endl << endl;
cout << "Moving main's ownership to Party2" << endl;
Party2->Member = move(Frodo);
cout << "Frodo has "
<< Party1->Member.use_count()
<< " owners (Party1 and Party2)"
<< endl << endl;
cout << "Deleting Party2" << endl;
Party2.reset();
cout << "Frodo has "
<< Party1->Member.use_count()
<< " owner left (Party1)"
<< endl << endl;
cout << "Deleting Party1"
<< endl
<< "Frodo will have no owners left so "
<< "he will be automatically deleted"
<< endl << endl;
Party1.reset();
cout << "Main function ending" << endl;
}
Creating Frodo
Frodo has 1 owner (the main function)
Giving Party1 a copy
Frodo has 2 owners (main and Party1)
Moving main's ownership to Party2
Frodo has 2 owners (Party1 and Party2)
Deleting Party2
Frodo has 1 owner left (Party1)
Deleting Party1
Frodo will have no owners left so he will be automatically deleted
Deleting Frodo
Main function ending
By far, the most common mistake people make with shared pointers is not copying them when they intend to.
For example, the following code is not copying the shared pointer. Rather, it is creating a new shared pointer, from a raw pointer:
int main() {
auto Pointer1 { make_shared<Character>() };
shared_ptr<Character> Pointer2 { Pointer1.get() };
}
The mechanism that enables shared pointers to be “connected” is predicated on the copy operator. Therefore, for a new shared pointer to be connected to the existing set of pointers, it needs to be copied from one of the pointers in that set.
Creating a new shared pointer in any other way will bypass this mechanism, even if the shared pointer happens to be pointing to the same object as another shared pointer:
int x { 42 };
std::shared_ptr<int> Pointer1 { &x };
std::shared_ptr<int> Pointer2 { &x };
std::cout "Owners: " << Pointer1.use_count();
Owners: 1
Therefore, when we want to share ownership of an object, we should ensure we are triggering the copy constructor of the shared pointer.
int x { 42 };
std::shared_ptr<int> Pointer1 { &x };
std::shared_ptr<int> Pointer2 { Pointer1 };
std::cout "Owners: " << Pointer1.use_count();
Owners: 2
Just like raw pointers can be cast to other types with std::dynamic_cast
and std::static_cast
, so too can shared pointers.
The only difference is, for shared pointers, we instead use std::dynamic_pointer_cast
and std::static_pointer_cast
, available from including <memory>
We have dedicated lessons on static and dynamic casting available here:
An example of using std::dynamic_pointer_cast
being used with a std::shared_ptr
is below:
#include <iostream>
#include <memory>
class Character {
public:
virtual ~Character() = default;
};
class Monster : public Character {};
void Downcast(std::shared_ptr<Character> Attacker) {
auto MonsterPtr {
std::dynamic_pointer_cast<Monster>(Attacker)
};
if (MonsterPtr) {
std::cout << "It's a monster!\n";
} else {
std::cout << "It's not a monster!\n";
}
}
int main() {
// A character pointer pointing at a monster
std::shared_ptr<Character> CharacterPtrA {
std::make_shared<Monster>()
};
Downcast(CharacterPtrA);
// A character pointer pointing at a character
std::shared_ptr<Character> CharacterPtrB {
std::make_shared<Character>()
};
Downcast(CharacterPtrB);
}
It's a monster!
It's not a monster!
Shared pointers have an interesting feature where the object they are sharing ownership over need not be the same as the object they are pointing at. This is typically referred to as aliasing.
It can be done by passing a pointer as the second argument to the shared_ptr
 constructor:
int main() {
auto One { make_shared<int>(1) };
int Two { 2 };
shared_ptr<int> Alias { One, &Two };
cout << "Number Pointed At: " << *Alias << endl;
}
Number Pointed At: 2
Care should be used when doing this, as it can create a memory management setup that is very confusing.
The simplest and most useful application of this is to create a pointer that shares ownership of an object, whilst pointing to a specific member of that same object.
For example, below we have a smart pointer that points to a character’s Name
field, but shares ownership of the Character
as a whole.
This gives us a pointer we can use to directly access the specific object property we want, whilst also preventing the object itself from being deleted from under us:
int main() {
auto Frodo { make_shared<Character>() };
shared_ptr<string> Name { Frodo, &Frodo->Name };
cout << "Name: " << *Name << endl;
cout << "Owners: " << Name.use_count() << endl;
}
Name: Frodo
Owners: 2
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.