Skip to content
C++

Which Smart Pointer?

Decision guide for choosing between unique_ptr, shared_ptr, weak_ptr, and raw pointers in C++.

Quick Answer

cpp
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 observers

The full decision tree

Step 1: Do you need a pointer at all?

Most C++ objects should be local variables or value members:

cpp
// 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.

cpp
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 null

Shared ownership → std::shared_ptr

Multiple owners. Object lives until the last owner is released.

cpp
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 destroyed

Non-owning observation → std::weak_ptr

Observe a shared_ptr-managed object without extending its lifetime.

cpp
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:

cpp
void process(Widget* w);       // non-owning, not nullable
void process(Widget& w);       // preferred — non-owning, not null

Comparison table

unique_ptrshared_ptrweak_ptrraw ptr/ref
Owns objectYesYes (shared)NoNo
CopyableNoYesYesYes
Thread-safe ref countYesYes
OverheadNoneControl blockControl blockNone
NullableYesYesYesPointer: yes, Ref: no
Use forDefaultShared lifetimeBack-refs, cachesBorrowed access

Common scenarios

Factory functions

cpp
// Return unique_ptr — caller can convert to shared_ptr if needed
std::unique_ptr<Widget> createWidget();

Observer / callback

cpp
// 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)

cpp
struct Node {
    std::vector<std::shared_ptr<Node>> children;  // owns children
    std::weak_ptr<Node> parent;                   // back-link, no cycle
};

Cache

cpp
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 to shared_ptr only when you have concrete evidence that ownership is genuinely shared. Use weak_ptr to break cycles or observe without owning.