Which Smart Pointer?
Decision guide for choosing between unique_ptr, shared_ptr, weak_ptr, and raw pointers in C++.
Quick Answer
Do you need heap allocation?
No → Use a local variable (stack) or a value member.
Yes ↓
Is ownership shared between multiple owners?
No → std::unique_ptr ← default choice
Yes ↓
Do all owners have equal lifetime?
Yes → std::shared_ptr
No → shared_ptr for owners + weak_ptr for observersThe full decision tree
Step 1: Do you need a pointer at all?
Most C++ objects should be local variables or value members:
// Don't do this:
auto w = std::make_unique<Widget>();
// Do this (unless Widget is large or polymorphic):
Widget w;Use smart pointers when:
- The object's lifetime must outlast its enclosing scope
- You need polymorphism (base pointer to derived)
- The object is very large and you're passing it around
- Ownership needs to be transferred
Step 2: Who owns the object?
Exclusive ownership → std::unique_ptr
One owner. When the owner is destroyed, the object is destroyed.
auto w = std::make_unique<Widget>(); // I own this exclusively
// Transfer ownership:
void sink(std::unique_ptr<Widget> w); // takes ownership
sink(std::move(w)); // w is now nullShared ownership → std::shared_ptr
Multiple owners. Object lives until the last owner is released.
auto w = std::make_shared<Widget>(); // shared ownership
auto copy = w; // both own it — ref count 2
// Object deleted when both w and copy are destroyedNon-owning observation → std::weak_ptr
Observe a shared_ptr-managed object without extending its lifetime.
std::weak_ptr<Widget> observer = w; // doesn't extend lifetime
if (auto locked = observer.lock()) { // safe access — may be null
locked->doWork();
}Non-owning, lifetime guaranteed → raw pointer or reference
When you can guarantee the pointed-to object outlives the pointer:
void process(Widget* w); // non-owning, not nullable
void process(Widget& w); // preferred — non-owning, not nullComparison table
unique_ptr | shared_ptr | weak_ptr | raw ptr/ref | |
|---|---|---|---|---|
| Owns object | Yes | Yes (shared) | No | No |
| Copyable | No | Yes | Yes | Yes |
| Thread-safe ref count | — | Yes | Yes | — |
| Overhead | None | Control block | Control block | None |
| Nullable | Yes | Yes | Yes | Pointer: yes, Ref: no |
| Use for | Default | Shared lifetime | Back-refs, caches | Borrowed access |
Common scenarios
Factory functions
// Return unique_ptr — caller can convert to shared_ptr if needed
std::unique_ptr<Widget> createWidget();Observer / callback
// Store weak_ptr in callback; lock() before use
class Button {
std::vector<std::weak_ptr<Listener>> listeners;
public:
void click() {
for (auto& wl : listeners) {
if (auto l = wl.lock()) l->onClicked();
}
}
};Tree structure (parent/children)
struct Node {
std::vector<std::shared_ptr<Node>> children; // owns children
std::weak_ptr<Node> parent; // back-link, no cycle
};Cache
class Cache {
std::map<Key, std::weak_ptr<Value>> entries;
public:
std::shared_ptr<Value> get(Key k) {
auto& wp = entries[k];
if (auto v = wp.lock()) return v;
auto v = load(k);
wp = v;
return v;
}
};Rule of thumb
Default to
unique_ptr. Upgrade toshared_ptronly when you have concrete evidence that ownership is genuinely shared. Useweak_ptrto break cycles or observe without owning.