Memory & RAII
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
- Rewrite the
IntHolderexample usingstd::unique_ptrinstead of a raw pointer. - Create a
std::vector<std::unique_ptr<Shape>>, push aCircleinto it, and callarea()polymorphically. - Show that a
shared_ptrkeeps its object alive while a second owner exists. - Observe RAII in action: print "acquired" in a class constructor and "released" in the destructor, then create and destroy the object inside a scope.