std::weak_ptr
Introduced in C++11, std::weak_ptr is a smart pointer that holds a non-owning (weak) reference to an object managed by std::shared_ptr. Because it does not increment the reference count, it is used to break "circular references" — situations where two shared_ptr instances own each other, preventing the count from ever reaching zero and causing a memory leak. Replacing one side of the cycle with a weak_ptr solves the problem.
Syntax
// ========================================
// weak_ptr basics
// ========================================
#include <memory>
// Create from a shared_ptr (does not increase the reference count)
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
// Check validity
if (!wp.expired()) {
// Promote to shared_ptr with lock() before accessing
std::shared_ptr<int> locked = wp.lock();
if (locked) {
// safe to use the object through locked
}
}
// When sp is released, wp becomes expired
sp.reset();
bool invalid = wp.expired(); // true (object has been freed)
Syntax Reference
| Operation / Member | Description |
|---|---|
| std::weak_ptr<T> wp = sp | Creates a weak_ptr from a shared_ptr. The reference count is not incremented. |
| wp.lock() | Returns a shared_ptr if the observed object is still alive; returns an empty shared_ptr if it has already been freed. |
| wp.expired() | Returns true if the observed object has already been freed. |
| wp.use_count() | Returns the number of shared_ptr instances currently owning the observed object. |
| wp.reset() | Empties the weak_ptr. |
| Breaking circular references | In an "A owns B, B owns A" relationship, changing one side to weak_ptr prevents the cycle. |
Sample Code
eva_weak_ptr.cpp
// ========================================
// eva_weak_ptr.cpp
// Using the relationship between Eva pilots and NERV
// to demonstrate shared_ptr circular references and
// their resolution with weak_ptr
// ========================================
#include <iostream>
#include <memory>
#include <string>
// ========================================
// Forward declaration
// Required because Pilot and Nerv reference each other
// ========================================
class Nerv;
// ========================================
// Pilot class
// A pilot holds a reference to the NERV organization.
// Using weak_ptr avoids a circular reference.
// ========================================
class Pilot {
public:
std::string name;
// Reference to Nerv is weak_ptr (prevents circular reference)
std::weak_ptr<Nerv> organization;
Pilot(const std::string& name) : name(name) {
std::cout << "[create] Pilot: " << name << std::endl;
}
~Pilot() {
std::cout << "[free] Pilot: " << name << std::endl;
}
void report() {
// Promote to shared_ptr with lock() before accessing
std::shared_ptr<Nerv> org = organization.lock();
if (org) {
std::cout << name << " belongs to " << org->codeName << "." << std::endl;
} else {
std::cout << name << ": the organization no longer exists." << std::endl;
}
}
};
// ========================================
// Nerv class
// NERV holds strong ownership of its pilot.
// ========================================
class Nerv {
public:
std::string codeName;
// NERV owns the pilot, so shared_ptr is used
std::shared_ptr<Pilot> assignedPilot;
Nerv(const std::string& codeName) : codeName(codeName) {
std::cout << "[create] Nerv: " << codeName << std::endl;
}
~Nerv() {
std::cout << "[free] Nerv: " << codeName << std::endl;
}
};
// ========================================
// Function to demonstrate weak_ptr expiry check
// ========================================
void checkWeakPtr(const std::weak_ptr<Pilot>& wp) {
if (wp.expired()) {
std::cout << " -> weak_ptr is expired (object has been freed)." << std::endl;
} else {
std::shared_ptr<Pilot> locked = wp.lock();
std::cout << " -> weak_ptr is valid. Pilot: " << locked->name
<< " ref count: " << locked.use_count() << std::endl;
}
}
int main() {
// ========================================
// Part 1: Resolving circular reference with weak_ptr
// ----------------------------------------
// If Pilot::organization were shared_ptr<Nerv>:
// nerv -> pilot (shared_ptr) count: 1
// pilot -> nerv (shared_ptr) count: 1
// Neither count reaches 0 when the scope ends,
// so destructors are never called — memory leaks.
// ========================================
std::cout << "=== Part 1: Resolving circular reference with weak_ptr ===" << std::endl << std::endl;
{
std::shared_ptr<Nerv> nerv = std::make_shared<Nerv>("NERV HQ");
std::shared_ptr<Pilot> pilot = std::make_shared<Pilot>("Ikari Shinji");
// Set up the mutual references
nerv->assignedPilot = pilot; // Nerv owns Pilot (shared_ptr)
pilot->organization = nerv; // Pilot weakly references Nerv (weak_ptr)
std::cout << "nerv ref count: " << nerv.use_count() << std::endl;
std::cout << "pilot ref count: " << pilot.use_count() << std::endl;
// nerv: 1 (weak_ptr does not increment)
// pilot: 2 (nerv->assignedPilot + pilot itself)
std::cout << std::endl;
pilot->report(); // Access NERV info safely via weak_ptr.lock()
std::cout << std::endl;
std::cout << "--- Scope ends. Destructors are called in the correct order. ---" << std::endl;
}
// nerv freed -> assignedPilot (pilot) ref count drops to 1
// pilot freed -> destructor called
// Both destructors are called, confirming no memory leak
std::cout << std::endl;
std::cout << "=== Part 2: Checking weak_ptr expiry ===" << std::endl << std::endl;
std::weak_ptr<Pilot> weakAyanami;
{
std::shared_ptr<Pilot> ayanami = std::make_shared<Pilot>("Ayanami Rei");
weakAyanami = ayanami;
std::cout << "While shared_ptr is alive:" << std::endl;
checkWeakPtr(weakAyanami);
std::cout << std::endl;
std::cout << "--- Scope ends ---" << std::endl;
}
// ayanami shared_ptr goes out of scope and is freed
std::cout << std::endl;
std::cout << "After shared_ptr is freed:" << std::endl;
checkWeakPtr(weakAyanami); // expired() == true
std::cout << std::endl;
std::cout << "=== Part 3: Multiple pilots and use_count ===" << std::endl << std::endl;
std::shared_ptr<Pilot> asuka = std::make_shared<Pilot>("Soryu Asuka");
std::shared_ptr<Pilot> kaworu = std::make_shared<Pilot>("Nagisa Kaworu");
std::shared_ptr<Pilot> misato = std::make_shared<Pilot>("Katsuragi Misato");
std::weak_ptr<Pilot> wAsuka = asuka;
std::weak_ptr<Pilot> wKaworu = kaworu;
std::cout << std::endl;
std::cout << "asuka use_count: " << wAsuka.use_count() << " (1 shared_ptr owns it)" << std::endl;
std::cout << "kaworu use_count: " << wKaworu.use_count() << " (1 shared_ptr owns it)" << std::endl;
// Release kaworu
kaworu.reset();
std::cout << std::endl;
std::cout << "After kaworu.reset():" << std::endl;
checkWeakPtr(wKaworu); // expired() == true
std::cout << std::endl;
std::cout << "=== Done ===" << std::endl;
return 0;
}
# Compile g++ -std=c++11 eva_weak_ptr.cpp -o eva_weak_ptr # Run ./eva_weak_ptr === Part 1: Resolving circular reference with weak_ptr === [create] Nerv: NERV HQ [create] Pilot: Ikari Shinji nerv ref count: 1 pilot ref count: 2 Ikari Shinji belongs to NERV HQ. --- Scope ends. Destructors are called in the correct order. --- [free] Nerv: NERV HQ [free] Pilot: Ikari Shinji === Part 2: Checking weak_ptr expiry === [create] Pilot: Ayanami Rei While shared_ptr is alive: -> weak_ptr is valid. Pilot: Ayanami Rei ref count: 2 --- Scope ends --- [free] Pilot: Ayanami Rei After shared_ptr is freed: -> weak_ptr is expired (object has been freed). === Part 3: Multiple pilots and use_count === [create] Pilot: Soryu Asuka [create] Pilot: Nagisa Kaworu [create] Pilot: Katsuragi Misato asuka use_count: 1 (1 shared_ptr owns it) kaworu use_count: 1 (1 shared_ptr owns it) After kaworu.reset(): [free] Pilot: Nagisa Kaworu -> weak_ptr is expired (object has been freed). === Done === [free] Pilot: Katsuragi Misato [free] Pilot: Soryu Asuka
Common Mistakes
Here are two common pitfalls when using weak_ptr.
Forgetting to check the return value of lock()
lock() returns an empty shared_ptr (equivalent to nullptr) if the target object has already been freed. Accessing members without checking will cause a crash.
lock_check_mistake.cpp (NG)
// NG: using lock() return value without checking std::weak_ptr<std::string> wp; // ... after wp is set and sp has been freed ... std::shared_ptr<std::string> locked = wp.lock(); std::cout << *locked << std::endl; // crash (locked is nullptr)
lock_check_mistake.cpp (OK)
#include <iostream>
#include <memory>
#include <string>
int main() {
std::weak_ptr<std::string> wp;
{
auto sp = std::make_shared<std::string>("Ikari Shinji");
wp = sp;
}
// sp has gone out of scope and been freed here
// OK: always check the return value of lock() with if
std::shared_ptr<std::string> locked = wp.lock();
if (locked) {
std::cout << "Pilot: " << *locked << std::endl;
} else {
std::cout << "The object has already been freed." << std::endl;
}
return 0;
}
# Compile g++ -std=c++11 lock_check_mistake.cpp -o lock_check_mistake # Run ./lock_check_mistake The object has already been freed.
Overlooking the circular reference pattern
When A holds B via shared_ptr and B also holds A via shared_ptr, neither reference count ever reaches zero and destructors are never called. Changing one side to weak_ptr breaks the cycle.
circular_ref_bad.cpp (NG — circular reference: destructors not called)
#include <iostream>
#include <memory>
#include <string>
struct NervBad;
struct PilotBad {
std::string name;
std::shared_ptr<NervBad> org; // NG: shared_ptr causes a circular reference
PilotBad(const std::string& n) : name(n) {
std::cout << "[create] PilotBad: " << name << std::endl;
}
~PilotBad() { std::cout << "[free] PilotBad: " << name << std::endl; }
};
struct NervBad {
std::string code;
std::shared_ptr<PilotBad> pilot;
NervBad(const std::string& c) : code(c) {
std::cout << "[create] NervBad: " << code << std::endl;
}
~NervBad() { std::cout << "[free] NervBad: " << code << std::endl; }
};
int main() {
auto nerv = std::make_shared<NervBad>("NERV HQ");
auto pilot = std::make_shared<PilotBad>("Ayanami Rei");
nerv->pilot = pilot;
pilot->org = nerv;
// Scope ends but ref count stays at 1 — destructors never called (memory leak)
return 0;
}
circular_ref_good.cpp (OK — weak_ptr: destructors called correctly)
#include <iostream>
#include <memory>
#include <string>
struct NervGood;
struct PilotGood {
std::string name;
std::weak_ptr<NervGood> org; // OK: weak_ptr breaks the cycle
PilotGood(const std::string& n) : name(n) {
std::cout << "[create] PilotGood: " << name << std::endl;
}
~PilotGood() { std::cout << "[free] PilotGood: " << name << std::endl; }
};
struct NervGood {
std::string code;
std::shared_ptr<PilotGood> pilot;
NervGood(const std::string& c) : code(c) {
std::cout << "[create] NervGood: " << code << std::endl;
}
~NervGood() { std::cout << "[free] NervGood: " << code << std::endl; }
};
int main() {
auto nerv = std::make_shared<NervGood>("NERV HQ");
auto pilot = std::make_shared<PilotGood>("Ikari Shinji");
nerv->pilot = pilot;
pilot->org = nerv;
// weak_ptr does not increment ref count, so both are freed correctly
return 0;
}
# Compile g++ -std=c++11 circular_ref_good.cpp -o circular_ref_good # Run ./circular_ref_good [create] NervGood: NERV HQ [create] PilotGood: Ikari Shinji [free] NervGood: NERV HQ [free] PilotGood: Ikari Shinji
Overview
std::weak_ptr is a non-owning weak reference used to break circular reference problems with std::shared_ptr. Unlike shared_ptr, it does not increment the reference count and therefore does not affect the object's lifetime. To access the object, call lock() to promote it to a shared_ptr. If the original shared_ptr has already been freed, lock() returns an empty shared_ptr, so always check with expired() or if (locked) before use. Typical use cases include parent-child mutual references (parent holds child via shared_ptr; child holds parent via weak_ptr), caches, and observer patterns where you want to observe without owning. Using weak_ptr correctly further improves the safety of resource management with shared_ptr. See also shared_ptr and unique_ptr.
If you find any errors or copyright issues, please contact us.