Skip to content

Intermediate · Templates, smart pointers and move semantics · topic 28 of 42 · 30 min

Smart pointers 2: std::shared_ptr and std::weak_ptr

Explanation

std::shared_ptr lets several owners share one object. It keeps a reference count and deletes the object when the last shared_ptr goes away. It costs more than unique_ptr, so use it only when ownership is truly shared. std::weak_ptr watches a shared object without owning it, which breaks reference cycles.

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.

cpp
#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 deleted
make_shared allocates the object and its control block (which holds the counts) together, in one allocation.
Think of it like this: A shared_ptr is like the lights in a meeting room with a motion sensor that counts people. Each person entering adds one; each leaving subtracts one. The lights switch off only when the count reaches zero, no matter who leaves last.

The 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

cpp
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" printed
Two objects that own each other keep each other alive forever.

std::weak_ptr breaks the cycle

cpp
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";
}
A weak_ptr does not change the use count. lock() returns a temporary shared_ptr if the object still exists.

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

SituationUse
Object on the stack or inside a containerno pointer at all (a plain value)
One clear owner of a heap objectstd::unique_ptr
Several owners, lifetime ends with the last onestd::shared_ptr
Observe a shared object without owning itstd::weak_ptr
Use an object you do not own, never nullT& (reference)
Use an object you do not own, may be nullT* (raw, non-owning)
Careful: Do not create a shared_ptr from this inside a member function (std::shared_ptr<T>(this)); that makes a second, independent control block and a double delete. If an object needs a shared_ptr to itself, inherit from std::enable_shared_from_this<T> and call shared_from_this().

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

std::shared_ptr

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.

cpp
#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 ends
Shared, read-only data whose lifetime depends on several users is a good fit for shared_ptr. (Swapping it safely across threads needs atomic operations, covered in the concurrency topic.)

Practice (0/4 done)

Task 1 · Question

auto a = std::make_shared<int>(1); auto b = a; auto c = b; What is a.use_count()?

Task 2 · Question

What does weak.lock() return if the object has already been deleted?

Task 3 · Question

A Node owns its children with std::shared_ptr<Node>, and each child holds std::shared_ptr<Node> parent. What is the problem?

Task 4 · Practice

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

Question 1

1.When is an object managed by shared_ptr deleted?

Question 2

2.Why prefer std::make_shared over std::shared_ptr<T>(new T)?

Question 3

3.Does a weak_ptr keep its object alive?

Question 4

4.Which should be your DEFAULT choice for owning a heap object?

Read more (free)

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

Answer: sink(std::move(p))

Ownership must be moved explicitly.

From Smart pointers 1: std::unique_ptr

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.

From Copying objects: the rule of three, five and zero

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.

From std::optional, std::variant and std::string_view

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. 1 more topic in “Templates, smart pointers and move semantics” before its exam: Move semantics: steal instead of copy.
  2. Module 7 exam: Templates, smart pointers and move semantics · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Modern C++: lambdas, C++17 and C++20 essentials. 42 topics left in the course before the final exam.

← Previous: Smart pointers 1: std::unique_ptr