Menu
CodeSprout
Typo-tolerant search across every article, problem and post. to open

RAII and smart pointers: never leak again

RAII is the C++ idiom that ties a resource's lifetime to a stack object. Combined with unique_ptr, shared_ptr and weak_ptr, it eliminates whole classes of leaks and dangling pointers.

kvi5hnu2 min read
0comments
On this page

Memory leaks, double-frees, and dangling pointers in C++ almost always boil down to one question: who owns this? RAII (Resource Acquisition Is Initialization) answers it by making the owner a stack object whose destructor cleans up.

RAII in one example

A file handle wrapped in RAII cannot leak, even if the function throws:

cpp
struct File {
    FILE* f;
    File(const char* name, const char* mode) : f(std::fopen(name, mode)) {
        if (!f) throw std::runtime_error("open failed");
    }
    ~File() { if (f) std::fclose(f); }

    // disallow copying — two closers for one handle is a bug
    File(const File&) = delete;
    File& operator=(const File&) = delete;
};

void write_log(const char* path) {
    File f(path, "a");
    std::fputs("hello\n", f.f);
    // f.~File() runs here, whether we return normally or throw
}

The pattern is: acquire in the constructor, release in the destructor, and let the compiler do the rest. The standard library's smart pointers apply this exact pattern to heap memory.

unique_ptr — single owner

std::unique_ptr<T> owns a heap object exclusively. It cannot be copied (that would mean two owners); it can be moved (ownership transfers), and when it goes out of scope it deletes what it owns.

cpp
auto u = std::make_unique<Widget>(42);
process(std::move(u));  // ownership moves; u is now empty
// if we hadn't moved, u.~unique_ptr<Widget>() would delete Widget here

Pick unique_ptr when: there is exactly one owner at a time. That is most heap allocations.

Zero runtime overhead compared to a raw pointer with a manual delete.

shared_ptr — shared ownership (use sparingly)

std::shared_ptr<T> keeps a reference count. The object is deleted when the last shared_ptr to it is destroyed.

cpp
auto s = std::make_shared<Widget>();  // refcount = 1
auto s2 = s;                          // refcount = 2
// both go out of scope: refcount drops to 0, delete runs

Pick shared_ptr when: ownership genuinely branches at runtime (a resource outlives any single caller, multiple threads juggle it, or a graph with no clear owner). It costs: an atomic refcount, a control block allocation (unless you use make_shared), and silent cycles if two shared_ptrs hold each other.

If you are reaching for shared_ptr because you cannot figure out who should own something — stop and redesign. A clear owner is almost always possible.

weak_ptr — break the cycle

A weak_ptr observes a shared_ptr without extending its lifetime. To use it, promote it to a shared_ptr with .lock(); if the object is already gone, you get an empty one.

cpp
std::shared_ptr<Node> head = std::make_shared<Node>();
std::weak_ptr<Node> cache = head;

if (auto p = cache.lock()) {
    p->touch();  // still alive
} else {
    // head released it; refetch or skip
}

The classic use: a parent holds a shared_ptr to a child, and the child holds a weak_ptr back to the parent. Without the weak_ptr, the two refcounts never drop to zero and the pair leaks forever.

The rule I live by

Prefer the stack. If it must be on the heap, prefer unique_ptr. Reach for shared_ptr only when ownership is genuinely shared, and add a weak_ptr wherever that creates a cycle.

Raw new and delete almost never belong in modern C++. If you see them in code you are writing today, you are almost certainly a make_unique away from a safer version.

Written by

kvi5hnu

More in C++

Discussion

Sign in to ask a question or share what you learned.