C++
Memory Management
C++ has its own version of C's malloc/free
— new and delete — with the exact same manual-matching-pair risk. Smart
pointers are C++'s real answer: RAII,
applied directly to raw memory, making "forgot to free it" a non-issue rather than a discipline problem.
- What it is
new/delete: C++'s manual heap allocation, analogous tomalloc/free. Smart pointers: classes that own a heap allocation and free it automatically via their destructor.- Mental model
- A smart pointer is a small wrapper object that acts like a raw pointer for everyday use, but also holds a "when I die, free what I point to" promise — the same RAII trick applied to memory specifically.
- Key numbers
std::unique_ptrhas zero performance overhead versus a raw pointer;std::shared_ptrhas a small overhead for its internal reference count.- Reach for it when
- Any time you'd reach for
new— modern C++ practice treats a smart pointer as the default, and a raw owning pointer as the rare exception.
new and delete
int *p = new int(42); // allocate one int on the heap, initialized to 42
std::cout << *p << std::endl; // 42
delete p; // free it — required, exactly like C's free()
int *arr = new int[10]; // allocate an array
delete[] arr; // matching array-delete — required for array allocations
This has exactly the same failure mode as C's malloc/free: forget the matching
delete and the memory leaks for the life of the program; call delete twice on
the same pointer and the behavior is undefined. See
Embedded C's Dynamic Memory for the full picture of
why manual allocation is risky in the first place.
std::unique_ptr
std::unique_ptr owns a heap allocation exclusively — exactly one
unique_ptr can own a given object at a time, and it's freed automatically the instant the
unique_ptr itself goes out of scope:
#include <memory>
void useBuffer() {
std::unique_ptr<int[]> buffer(new int[100]);
buffer[0] = 42; // used exactly like a raw pointer
std::cout << buffer[0] << std::endl; // 42
} // buffer goes out of scope HERE — the array is freed automatically, no delete needed
This is precisely the RAII pattern from
Constructors, Destructors & RAII, applied
directly to a heap allocation — there's no delete anywhere in this function because there
doesn't need to be one.
std::shared_ptr
std::shared_ptr allows multiple owners of the same allocation, tracked with
an internal reference count — the memory is only freed once the last owning
shared_ptr goes out of scope:
std::shared_ptr<int> a = std::make_shared<int>(42);
std::cout << a.use_count() << std::endl; // 1
{
std::shared_ptr<int> b = a; // b shares ownership with a
std::cout << a.use_count() << std::endl; // 2
} // b goes out of scope, but the int is NOT freed — a still owns it
std::cout << a.use_count() << std::endl; // back to 1
// the int is finally freed when a itself goes out of scope
Ownership, visually
unique_ptr's ownership is exclusive and simple; a shared_ptr's reference count is what lets multiple owners exist safely, at the small cost of tracking that count.Real-world applications
- Default choice for owned heap data — modern C++ style guides (including at major companies) treat a raw owning
new/deletepair as something to justify, not the default. - Shared caches and object pools —
shared_ptris the natural fit when the same object genuinely needs to be reachable from multiple independent owners, each with their own lifetime. - Factory functions — a function that creates and returns an object commonly returns a
unique_ptr, transferring clear, single ownership to the caller.
Gotchas
- Mixing raw and smart pointers to the same object is a real hazard — a raw pointer taken from inside a
unique_ptrand used after theunique_ptris destroyed is a classic dangling-pointer bug, identical to the one covered in C's Pointers. shared_ptr's reference counting has real runtime and memory overhead versus a raw pointer orunique_ptr— reach for it specifically when shared ownership is genuinely needed, not as an all-purpose default.- Two
shared_ptrs that reference each other (a "reference cycle") never reach a count of zero, and neither is ever freed — a real, easy-to-introduce memory leak thatstd::weak_ptrexists specifically to break. - Smart pointers, like the STL containers in the previous chapter, add a heap-allocation dependency — the same tradeoff Embedded C's Dynamic Memory covers about avoiding dynamic allocation on tiny, real-time-constrained targets applies here too.