Lesson 9 / 25

RAII and Smart Pointers

Manage resources with RAII and choose between unique_ptr, shared_ptr and weak_ptr.

Resource Acquisition Is Initialisation

RAII is the most important idiom in C++: tie a resource's lifetime to an object's lifetime. The constructor acquires the resource (memory, a file, a lock, a socket) and the destructor releases it. Because destructors run automatically when objects go out of scope, including during exception unwinding, resources can never leak. Standard examples: std::vector (memory), std::fstream (files), std::lock_guard (mutexes) and smart pointers for heap objects. std::unique_ptr<T> expresses exclusive ownership: exactly one owner, zero overhead compared with a raw pointer, movable but not copyable; create it with std::make_unique. std::shared_ptr<T> expresses shared ownership with a reference count; the object is destroyed when the last owner goes away; create with std::make_shared. It costs more (atomic counting, a control block), so use it only when ownership is genuinely shared. std::weak_ptr<T> observes a shared object without owning it, breaking reference cycles (for example a child pointing back to its parent) that would otherwise leak.

Choosing a smart pointer

unique_ptr by default, shared_ptr when ownership is shared, weak_ptr to break cycles.

#include <memory>
#include <string>
#include <vector>

struct Engine { int hp; };

struct Car {
    std::unique_ptr<Engine> engine = std::make_unique<Engine>(Engine{120});  // exclusive owner
};

struct Node {
    std::string name;
    std::vector<std::shared_ptr<Node>> children;   // parent owns children
    std::weak_ptr<Node> parent;                    // child observes parent: no cycle
};

int main() {
    Car a;
    Car b = std::move(a);          // ownership of the engine moves to b; a.engine is now null

    auto root = std::make_shared<Node>(Node{"root"});
    auto leaf = std::make_shared<Node>(Node{"leaf"});
    leaf->parent = root;
    root->children.push_back(leaf);

    if (auto p = leaf->parent.lock()) {          // temporary shared_ptr if parent still alive
        // use p->name
    }
}   // everything is released automatically here

unique_ptr first

Start with std::unique_ptr. Switch to std::shared_ptr only when several parts of the program genuinely co-own an object and none can be designated the single owner.

Quick check: Which smart pointer expresses exclusive, single ownership with no reference-counting overhead?

  • std::shared_ptr
  • std::weak_ptr
  • std::unique_ptr
  • A raw pointer
Answer

std::unique_ptr — unique_ptr has exactly one owner and is as cheap as a raw pointer.