Loading...

Introduction to Smart Pointers

Getting started with unique_ptr, shared_ptr, and weak_ptr for safer lifetimes.

In C++, dynamic memory management is traditionally done with new and delete. However, manual memory management can lead to issues such as leaks, double deletion, or dangling pointer errors. Modern C++ (C++11 and later) introduced the concept of smart pointers to solve these problems. Smart pointers automate memory management and eliminate most of these errors.


1. What is a Smart Pointer?

A smart pointer is a special C++ class that manages the lifetime of an object. It behaves like a normal pointer but automatically controls the object’s lifecycle (creation and destruction). This helps prevent memory leaks.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> ptr = make_unique<int>(42);
    cout << *ptr << endl; // 42
}

There are three main smart pointer types: unique_ptr, shared_ptr, and weak_ptr. All of them are defined in the <memory> header.


2. unique_ptr

std::unique_ptr is a pointer whose ownership is held by exactly one object. Only one unique_ptr can own a given object at a time. It cannot be copied, but it can be moved (using std::move()).


#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> p1 = make_unique<int>(10);
    cout << *p1 << endl;

    // unique_ptr cannot be copied:
    // unique_ptr<int> p2 = p1; // ERROR!

    // But it can be moved:
    unique_ptr<int> p2 = move(p1);
    cout << *p2 << endl;
}

Advantages:

Disadvantage:
unique_ptr cannot be copied, so it’s not suitable when the object must be shared.


3. shared_ptr

std::shared_ptr allows multiple pointers to share ownership of the same object. The object’s lifetime depends on the number of owners (the reference count). When the last shared_ptr is destroyed, the object is deleted automatically.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> p1 = make_shared<int>(100);
    shared_ptr<int> p2 = p1; // shared ownership

    cout << "p1: " << *p1 << ", p2: " << *p2 << endl;
    cout << "Use count: " << p1.use_count() << endl; // 2
}

Advantages:

Disadvantages:


4. weak_ptr

std::weak_ptr is a “non-owning” reference to an object managed by a shared_ptr. A weak_ptr does not extend the lifetime of the object; it only observes it. This prevents circular reference problems.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> sp = make_shared<int>(50);
    weak_ptr<int> wp = sp;

    cout << "Use count: " << sp.use_count() << endl;

    if (auto ptr = wp.lock()) {
        cout << "Value: " << *ptr << endl;
    }
}

wp.lock() returns a valid shared_ptr if the object still exists; if the object has been destroyed, it returns nullptr. This provides safe access.


5. Circular Reference Problem

If two shared_ptr objects point to each other, the reference counts never reach zero and the memory is never freed. This typically occurs in linked or tree-like structures.


#include <memory>
using namespace std;

struct Node {
    shared_ptr<Node> next;
    // PROBLEM: circular reference!
};

int main() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    n1->next = n2;
    n2->next = n1; // endless reference loop
}

The fix is to use a weak_ptr for one of the links:


struct Node {
    weak_ptr<Node> next; // no more reference cycle
};

6. Using Smart Pointers with Functions

Smart pointers can be passed to functions just like regular pointers. However, you must pay attention to the ownership model.


void Print(shared_ptr<int> p) {
    cout << *p << endl;
}

int main() {
    auto ptr = make_shared<int>(77);
    Print(ptr); // safe shared use
}

If ownership should be transferred, pass a unique_ptr and use std::move().


void Take(unique_ptr<int> p) {
    cout << *p << endl;
}

int main() {
    auto p = make_unique<int>(88);
    Take(move(p)); // p is now nullptr
}

7. Advantages of Smart Pointer Memory Management


8. Comparison Table

Type Ownership Copyable Reference Counting Usage
unique_ptr Single No No Objects with a single owner
shared_ptr Shared Yes Yes Objects shared across multiple places
weak_ptr No ownership Yes Depends on a shared_ptr Breaking circular references

9. TL;DR

  • unique_ptr → single ownership, non-copyable, movable.
  • shared_ptr → shared ownership with reference counting.
  • weak_ptr → observes a shared object without owning it.
  • Smart pointers greatly reduce memory leaks.
  • Use weak_ptr to break circular references.
  • All examples can be run with Visual Studio 2022 or GCC 11+ compilers.

Related Articles