Skip to main content

Smart Pointers in Modern C++

Master C++11 smart pointers through interactive examples. Learn unique_ptr, shared_ptr, and weak_ptr with reference counting visualizations.

Smart Pointers: Modern C++ Memory Management

Smart pointers were introduced in C++11 to provide automatic memory management while maintaining the performance characteristics of C++. They use RAII to ensure proper resource cleanup and eliminate most memory-related bugs.

Helpful background

RAII and scope-bound cleanup

Connect resource cleanup to object destruction, including exception unwinding.

Open full page

The Three Smart Pointers

  1. std::unique_ptr: Exclusive ownership
  2. std::shared_ptr: Shared ownership with reference counting
  3. std::weak_ptr: Non-owning observation
Smart Pointers Deep Dive

Exclusive ownership - exactly one owner

unique_ptr
// Creating unique_ptr
auto ptr = std::make_unique<Widget>(args);

// Transfer ownership
auto ptr2 = std::move(ptr);  // ptr is now nullptr

// Automatic cleanup when out of scope
{
    auto local = std::make_unique<Resource>();
} // Resource destroyed here

// Custom deleter
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
    fopen("data.txt", "r"), &fclose
);
Unique ownership
auto ptr1 = std::make_unique<Widget>();
make_unique
0x55a1d4e0
32 bytes
Owner: ptr1
0x7fff1000
0x55a1d4e0
ptr18B (qword)
0x7fff2000
nullptr
ptr28B (qword)
0x7fff3000
nullptr
ptr38B (qword)
Step 1: Create unique_ptr
Key Takeaways
Smart Pointers Best Practices:
  • • Use unique_ptr by default for single ownership
  • • Use shared_ptr only when you need shared ownership
  • • Use weak_ptr to break circular references
  • • Prefer make_unique and make_shared for creation
Common Pitfalls:
  • • Never mix raw pointers with smart pointers for the same object
  • • Watch out for circular references with shared_ptr
  • • Always check weak_ptr validity before use

Unique Pointer (std::unique_ptr)

Characteristics

  • Exclusive ownership: Only one unique_ptr can own a resource
  • Move semantics: Ownership can be transferred but not copied
  • Zero overhead: Same performance as raw pointers
  • Custom deleters: Support for custom cleanup functions

Usage Examples

C++
// Creating unique_ptr
auto ptr = std::make_unique<Widget>(args);

// Transfer ownership
auto ptr2 = std::move(ptr);  // ptr becomes nullptr

// Custom deleter
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
    fopen("data.txt", "r"), &fclose
);

Shared Pointer (std::shared_ptr)

Characteristics

  • Shared ownership: Multiple pointers can own the same resource
  • Reference counting: Tracks how many pointers reference the object
  • Thread-safe counting: Reference count operations are atomic
  • Automatic cleanup: Object deleted when count reaches zero

Reference Counting Process

  1. Create shared_ptr → count = 1
  2. Copy shared_ptr → count increments
  3. Destroy shared_ptr → count decrements
  4. Count reaches 0 → object deleted

Circular Reference Problem

Break ownership cycles with weak_ptr

Use weak_ptr for non-owning links that break circular references, as the parent link below does. A weak pointer observes a shared object without affecting its reference count.

C++
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> parent;  // Break cycle with weak_ptr
};

Weak Pointer (std::weak_ptr)

Characteristics

  • Non-owning: Doesn't affect reference count
  • Observer pattern: Safely observe shared_ptr objects
  • Cycle breaking: Prevents circular references
  • Expiration checking: Can detect if object still exists

Usage Pattern

C++
std::shared_ptr<Widget> shared = std::make_shared<Widget>();
std::weak_ptr<Widget> weak = shared;

if (auto locked = weak.lock()) {
    // Use locked as shared_ptr
    locked->doSomething();
} else {
    // Object was destroyed
}

Best Practices

  1. Prefer make_unique and make_shared for creation
  2. Use unique_ptr by default, shared_ptr when sharing needed
  3. Pass raw pointers to functions that don't affect ownership
  4. Return smart pointers from factory functions
  5. Use custom deleters for non-standard cleanup

Performance Considerations

  • unique_ptr: Zero overhead compared to raw pointers
  • shared_ptr: Small overhead for reference counting
  • weak_ptr: Minimal overhead, but requires lock() to access
  • make_shared: More efficient than shared_ptr(new T())

Smart pointers represent modern C++'s answer to memory management - providing safety without sacrificing performance.

If you found this explanation helpful, consider sharing it with others.

Mastodon