Memory & RAII

Scope: C++ gives you control over memory, and with control comes responsibility. This lesson explains where objects live (stack vs heap), the dangers of manual new/delete, the RAII principle that makes C++ memory safe by design, and the smart pointers that automated it.

Stack vs heap

Objects live in one of two regions. Local variables sit on the stack: allocation is instant and automatic — the object is destroyed when its scope ends. The heap is a shared pool for objects whose lifetime must outlive the creating function; you request space and are responsible for releasing it.

void example() {
    int local = 42;         // stack: automatic, dies at the closing brace
    int *heap = new int(7); // heap: survives until delete, you manage it
    delete heap;            // must release heap memory exactly once
}

Stack allocation is the default choice — fast, safe, automatic. Use the heap only when the size is unknown at compile time or the object must outlive the function.

new and delete

Manual heap management uses new to create and delete to destroy. The rules are unforgiving: every new needs exactly one matching delete, and the deletion must happen after the last use.

int *p = new int(7);   // allocate one int on the heap
std::cout << *p;        // use it
delete p;              // release it — exactly once
p = nullptr;           // avoid double-delete accidents

Leaks and dangling

Two classic failures: forgetting the delete leaks memory (the block stays reserved forever, invisible to the program), and deleting twice, or using after deletion, is undefined behavior. Both are silent and hard to debug:

int *q = new int(5);
// ... no delete anywhere ...      // LEAK: memory stays reserved

delete q;
delete q;                            // BAD: double delete — undefined behavior
*q = 9;                              // BAD: use after delete — dangling access

Manual new/delete is how the older C++ literature wrote code; modern C++ wraps these calls in smart pointers (below) so the mistakes become impossible.

RAII

RAII (Resource Acquisition Is Initialization) is C++'s core safety idea: a class acquires a resource in its constructor and releases it in its destructor. Because destructors run automatically when an object goes out of scope, the release can never be forgotten — even when the function exits early or an exception flies by.

class IntHolder {
public:
    explicit IntHolder(int value) : data(new int(value)) {}  // acquire
    ~IntHolder() { delete data; }                            // release
private:
    int *data;
};

{
    IntHolder h(10);   // allocated here
    // ... using h ...
}                      // destructor frees the memory automatically

Ownership

RAII turns the question "who frees this?" into "which object owns it?". The owner is the object whose destructor releases the resource, and there must be exactly one owner at any time. This single rule eliminates leaks and double frees by construction.

Smart pointers

The standard library ships RAII wrappers for raw pointers. std::unique_ptr is the default: it owns the object exclusively and frees it automatically.

#include <memory>

std::unique_ptr<int> p = std::make_unique<int>(7);  // allocate
std::cout << *p << "\n";      // use like a pointer
// no delete needed — released when p goes out of scope

std::shared_ptr supports several owners: the object dies when the last owner releases it. The cost is a small bookkeeping counter; use it only when sharing is truly needed.

std::shared_ptr<double> a = std::make_shared<double>(3.14);
std::shared_ptr<double> b = a;      // two owners now
// the value lives until both a and b are gone

Prefer make_unique/make_shared over bare new: they are exception-safe and shorter. Containers such as std::vector use RAII internally too — that is why they "just work".

Rule of zero/three/five

A class that owns a resource must define the functions that copy and destroy it consistently. The rule of zero says: if a class uses only members that manage their own resources (smart pointers, containers, strings), write no special functions — the defaults are correct.

class Note {                          // rule of zero in action
public:
    Note(std::string text) : text(std::move(text)) {}
    void print() const { std::cout << text << "\n"; }
private:
    std::string text;                 // std::string owns its memory
};

If you must manage a raw resource, follow the rule of three/five: define the destructor, copy constructor, and copy assignment (and in modern C++, the move counterparts) together. If you define any one, define them all. In practice, smart pointers let almost all classes follow the rule of zero.

Practice

  1. Rewrite the IntHolder example using std::unique_ptr instead of a raw pointer.
  2. Create a std::vector<std::unique_ptr<Shape>>, push a Circle into it, and call area() polymorphically.
  3. Show that a shared_ptr keeps its object alive while a second owner exists.
  4. Observe RAII in action: print "acquired" in a class constructor and "released" in the destructor, then create and destroy the object inside a scope.