Intermediate · Templates, smart pointers and move semantics · topic 28 of 42 · 30 min
Smart pointers 2: std::shared_ptr and std::weak_ptr
Explanation
Shared ownership
Sometimes no single part of the program is clearly "the owner". An image might be used by the editor window, the thumbnail strip and the upload queue; it should stay alive until ALL of them are done. std::shared_ptr handles this. Every copy of a shared_ptr increases a reference count (use count); every destroyed copy decreases it; when the count reaches zero, the object is deleted.
#include <iostream>
#include <memory>
#include <string>
int main() {
auto photo = std::make_shared<std::string>("IMG_2041.jpg");
std::cout << photo.use_count() << '\n'; // 1
{
auto inEditor = photo; // copy: shared ownership
auto inUpload = photo;
std::cout << photo.use_count() << '\n'; // 3
} // two copies destroyed
std::cout << photo.use_count() << '\n'; // 1
} // last owner gone: string deletedThe cost of sharing
- A shared_ptr is two pointers wide (the object and the control block).
- The reference count is updated atomically, so it is thread-safe but slower than a plain integer change.
- Ownership becomes harder to reason about: "who keeps this alive?" may have many answers.
So the rule is: use std::unique_ptr by default; use std::shared_ptr only when several parts genuinely need to keep the object alive. A unique_ptr can be turned into a shared_ptr later (std::shared_ptr<T> s = std::move(u);), but not the other way round.
Reference cycles: the shared_ptr leak
struct Person {
std::string name;
std::shared_ptr<Person> friendOf; // owning link
~Person() { std::cout << "bye " << name << '\n'; }
};
void cycle() {
auto a = std::make_shared<Person>();
a->name = "Asha";
auto b = std::make_shared<Person>();
b->name = "Ravi";
a->friendOf = b;
b->friendOf = a; // a owns b and b owns a
} // counts drop to 1, never 0: both leak, no "bye" printedstd::weak_ptr breaks the cycle
struct Person {
std::string name;
std::weak_ptr<Person> friendOf; // non-owning link
};
auto a = std::make_shared<Person>();
auto b = std::make_shared<Person>();
a->friendOf = b;
b->friendOf = a; // no cycle of ownership
if (auto f = a->friendOf.lock()) { // lock() gives a shared_ptr, or empty if gone
std::cout << "friend is alive\n";
}Typical uses of weak_ptr: back-links from child to parent in a tree (the parent owns the children with shared_ptr or unique_ptr; children point back with weak_ptr or a raw pointer), caches that should not keep objects alive, and observers that must not stop the subject from dying.
Choosing an owner
| Situation | Use |
|---|---|
| Object on the stack or inside a container | no pointer at all (a plain value) |
| One clear owner of a heap object | std::unique_ptr |
| Several owners, lifetime ends with the last one | std::shared_ptr |
| Observe a shared object without owning it | std::weak_ptr |
| Use an object you do not own, never null | T& (reference) |
| Use an object you do not own, may be null | T* (raw, non-owning) |
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Smart pointers 2: std::shared_ptr and std::weak_ptr · 1/6
Sometimes an object has several owners. std shared pointer handles that.
Prefer a video by a teacher? Find free videos on “c++ shared_ptr weak_ptr reference counting cycle tutorial” on YouTube ↗
In real life: Shared configuration in a server
A web server loads its configuration (timeouts, feature flags) into an object. Each incoming request handler grabs a std::shared_ptr to the current config. When the admin reloads the configuration, the server swaps in a new shared_ptr; requests that started earlier keep using the old config safely, and the old object is freed automatically when the last of those requests finishes.
#include <iostream>
#include <memory>
struct Config { int timeoutMs; bool newCheckout; };
int main() {
auto current = std::make_shared<const Config>(Config{3000, false});
auto requestA = current; // request A holds v1
current = std::make_shared<const Config>(Config{2000, true}); // admin reload: v2
std::cout << requestA->timeoutMs << ' ' << current->timeoutMs << '\n'; // 3000 2000
} // v1 is freed when requestA ends; v2 when current endsPractice (0/4 done)
auto a = std::make_shared<int>(1); auto b = a; auto c = b; What is a.use_count()?
What does weak.lock() return if the object has already been deleted?
A Node owns its children with std::shared_ptr<Node>, and each child holds std::shared_ptr<Node> parent. What is the problem?
Write a small program with a struct Document and three "viewers" (a std::vector<std::shared_ptr<Document>>). Print the use_count after adding each viewer and after clearing the vector. Then add a std::weak_ptr<Document> that checks with lock() whether the document is still alive after the last shared_ptr is reset.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Move semantics: steal instead of copyTemplates, smart pointers and move semantics · same module
- Templates: code for any typeTemplates, smart pointers and move semantics · same module
- Rc, RefCell and WeakRust Programming · Closures, iterators and smart pointers
- std::optional, std::variant and std::string_viewModern C++: lambdas, C++17 and C++20 essentials
- Arrays, std::array and a first look at std::vectorReferences, pointers, arrays and strings
- Concurrency 2: atomics, async, futures and condition variablesExceptions, concurrency and building projects with CMake
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
When is an object managed by shared_ptr deleted?
Answer: When the last shared_ptr owning it is destroyed or reset
The use count reaching zero triggers deletion.
Why prefer std::make_shared over std::shared_ptr<T>(new T)?
Answer: One allocation for object and control block, and no raw new in your code
make_shared is faster and safer.
Does a weak_ptr keep its object alive?
Answer: No, it does not change the use count
weak_ptr observes without owning.
Which should be your DEFAULT choice for owning a heap object?
Answer: std::unique_ptr
unique_ptr is cheaper and makes ownership obvious; upgrade to shared_ptr only when needed.
How do you pass ownership of a unique_ptr into a function sink(std::unique_ptr<T> p)?Related
Why can a cycle of Rc pointers leak memory?Related
Answer: Neither count can reach zero, so nothing is freed
Each keeps the other alive.
From Rc, RefCell and Weak
Which pair makes a deep copy automatically when you copy the object?Related
Answer: A std::vector member
std::vector copies all its elements into new memory.
What is std::nullopt?Related
Answer: The value that means "this optional is empty"
Returning std::nullopt from a function returning std::optional<T> gives an empty optional.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Templates, smart pointers and move semantics” before its exam: Move semantics: steal instead of copy.
- Module 7 exam: Templates, smart pointers and move semantics · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Modern C++: lambdas, C++17 and C++20 essentials. 42 topics left in the course before the final exam.