Skip to content
Smart Pointers: unique_ptr and shared_ptr
step 1/7

Reading — step 1 of 7

Learn

~3 min readPointers and Memory

Modern C++ replaces raw new/delete with smart pointers — wrappers that own a heap object and automatically delete it when they go out of scope. This is RAII (Resource Acquisition Is Initialization) applied to memory management.

std::unique_ptr — sole ownership

#include <memory>

std::unique_ptr<int> p = std::make_unique<int>(42);
std::cout << *p;     // 42
// when p goes out of scope, the int is automatically deleted

unique_ptr<T> represents single ownership of a heap object. When it dies, it deletes. You never write delete.

Move-only — can't copy

std::unique_ptr<int> p1 = std::make_unique<int>(5);
// std::unique_ptr<int> p2 = p1;       // ✗ compile error — can't copy
std::unique_ptr<int> p2 = std::move(p1);  // ✓ ownership moved
// p1 is now empty (nullptr); p2 owns the int

The move semantics enforce single-ownership at compile time. If you have a unique_ptr, you KNOW no one else owns the same memory.

Returning from functions

std::unique_ptr<int> makeInt() {
    return std::make_unique<int>(42);   // returned by move
}

auto p = makeInt();    // p owns the int

std::shared_ptr — shared ownership

When multiple parts of your code need to OWN the same object (graph nodes, observer patterns):

#include <memory>

auto p1 = std::make_shared<std::string>("hello");
auto p2 = p1;          // ✓ copy — increments ref count
auto p3 = p1;          // ref count is now 3

std::cout << p1.use_count();   // 3
// when all three go out of scope, the string is freed

Reference-counted ownership. Each copy bumps the count; each destruction decrements. When count hits zero, delete runs.

Trade-off: shared_ptr has overhead (the control block holding the count) and atomic operations on copy/destroy. Use unique_ptr unless you genuinely need shared ownership.

std::weak_ptr — non-owning observer

Used to break reference cycles in shared_ptr graphs:

auto p = std::make_shared<int>(42);
std::weak_ptr<int> wp = p;     // doesn't increment ref count

if (auto locked = wp.lock()) {  // try to obtain a shared_ptr
    std::cout << *locked;        // ✓ p is still alive
}
p.reset();                       // last shared_ptr gone
if (auto locked = wp.lock()) {
    // doesn't run — int already deleted
}

Weak pointers are how you observe a shared object without keeping it alive.

Custom deleters

For non-default cleanup (closing files, releasing locks):

auto fileDeleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(fileDeleter)> file(fopen("data.txt", "r"), fileDeleter);
// file auto-closes when out of scope

When to use which

  • unique_ptr — default. Single owner, clear lifecycle. Cheaper than shared_ptr.
  • shared_ptr — when multiple owners genuinely exist. Often signals a design that could be simpler.
  • weak_ptr — observers, breaking shared_ptr cycles. Caches that shouldn't keep their entries alive.
  • Raw pointers (T*) — non-owning observation, optional/nullable parameters. Document with comments that you don't own them.

Common mistakes

  • Mixing new with smart pointers: unique_ptr<T> p(new T) works but make_unique is preferred (exception-safe, less typing).
  • Storing the same raw pointer in multiple smart pointers → double-free. Always use the same smart-pointer instance, not raw new wrapped twice.
  • shared_ptr cycles — A holds shared_ptr<B>, B holds shared_ptr<A> → both leak. Use weak_ptr for one direction.
  • Returning a raw pointer to a smart_ptr-managed object — risk of dangling once the smart_ptr dies. Document ownership.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…