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.
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:
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.
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.
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.
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
More in C++
Valid Parentheses
Decide whether a string of brackets is balanced. The classic stack warm-up — print "true" or "false" for one input per line.
References vs pointers in modern C++
A reference is a safer, cleaner default than a pointer in most places you used to pass a pointer. Here is a decision guide for picking between the two in modern C++.
Discussion
Sign in to ask a question or share what you learned.