shared_ptr & weak_ptr
std::shared_ptr allows multiple smart pointers to jointly own the same heap object. Internally, it keeps a shared reference count — a number tracking how many shared_ptr instances currently point at the object. The object is only deleted once that count drops to zero.
Creating a shared_ptr
shared_ptr_basics.cpp
CPP
#include <memory>
#include <iostream>
struct Resource {
~Resource() { std::cout << "Resource destroyed" << std::endl; }
};
int main() {
std::shared_ptr<Resource> a = std::make_shared<Resource>();
std::cout << "Count: " << a.use_count() << std::endl; // 1
{
std::shared_ptr<Resource> b = a; // copying increments the count
std::cout << "Count: " << a.use_count() << std::endl; // 2
} // b is destroyed here, count drops back to 1
std::cout << "Count: " << a.use_count() << std::endl; // 1
return 0;
} // a is destroyed here, count drops to 0 -> "Resource destroyed"The reference count in action
Each time a shared_ptr is copied, the count goes up. Each time a copy is destroyed (goes out of scope, is reset, or is reassigned), the count goes down. Only when the very last owner disappears does the count reach zero and the underlying object get deleted — automatically, with no manual tracking required.
The circular reference problem
Reference counting has one serious blind spot: if two objects hold shared_ptrs pointing at each other, each keeps the other's count above zero forever — even if nothing outside the pair still refers to either of them. Neither object is ever deleted, which is a memory leak despite using smart pointers correctly everywhere else.
Two shared_ptrs pointing at each other never reach zero
circular_reference.cpp
CPP
#include <memory>
struct B;
struct A {
std::shared_ptr<B> bPtr;
~A() { std::cout << "A destroyed" << std::endl; }
};
struct B {
std::shared_ptr<A> aPtr;
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->bPtr = b; // A holds a shared_ptr to B
b->aPtr = a; // B holds a shared_ptr to A -- circular!
return 0;
// Neither "A destroyed" nor "B destroyed" is ever printed:
// a and b's mutual references keep both counts at 1 forever. Leaked.
}weak_ptr — the fix for reference cycles
std::weak_ptr is a non-owning reference to an object managed by a shared_ptr. Holding a weak_ptr does not increase the reference count, so it cannot keep an object alive by itself — and it cannot create a cycle. To actually use the object, a weak_ptr must be temporarily converted into a shared_ptr via .lock(), which returns either a valid shared_ptr (if the object is still alive) or an empty one (if it has already been destroyed).
weak_ptr_fix.cpp
CPP
#include <memory>
#include <iostream>
struct B;
struct A {
std::shared_ptr<B> bPtr;
~A() { std::cout << "A destroyed" << std::endl; }
};
struct B {
std::weak_ptr<A> aPtr; // weak_ptr breaks the cycle
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->bPtr = b; // A owns B (shared_ptr)
b->aPtr = a; // B only observes A (weak_ptr) -- no cycle
if (auto locked = b->aPtr.lock()) {
std::cout << "A is still alive, use_count: "
<< locked.use_count() << std::endl;
}
return 0;
// Now both "A destroyed" and "B destroyed" print correctly.
}shared_ptr: reference-counted shared ownership; the object is freed when the last shared_ptr referencing it is destroyed.
Copying a shared_ptr increments the count; destroying a copy decrements it.
A cycle of shared_ptrs referencing each other leaks memory, since neither count ever reaches zero.
weak_ptr observes a shared_ptr's object without owning it or affecting the count -- use .lock() to safely access it.
Rule of thumb
Default to unique_ptr. Reach for shared_ptr only when ownership must genuinely be shared across multiple parts of the program, and use weak_ptr for any “back-reference” that would otherwise create a cycle — for example, a child object that needs to refer back to its parent.