Memory Management¶
Managing memory efficiently and safely is one of C++'s core strengths. Modern C++ automates much of this using RAII and Smart Pointers, making manual new and delete largely obsolete.
Stack vs. Heap¶
Understanding where your data lives is crucial.
The Stack¶
- Fast: Allocation is just moving a pointer.
- Automatic: Variables are destroyed when they go out of scope.
- Limited Size: Large arrays can cause a stack overflow.
The Heap (Free Store)¶
- Flexible: You control the lifetime.
- Large: Limited only by system memory.
- Slower: Allocation involves finding free blocks.
- Manual Management: Requires explicit cleanup (or smart pointers).
Pointers vs. References¶
Both refer to data elsewhere in memory, but they have key differences.
References (&)¶
- Must be initialized when declared.
- Cannot be null.
- Cannot be reseated (cannot refer to a different object later).
- Syntax: Use like a normal variable.
Pointers (*)¶
- Can be uninitialized (dangerous!).
- Can be null (
nullptr). - Can be reseated.
- Syntax: Use
*to dereference (access value) and->to access members.
RAII (Resource Acquisition Is Initialization)¶
This is the most important idiom in C++. It binds the life cycle of a resource (memory, file, lock) to the life cycle of an object.
- Constructor: Acquires the resource.
- Destructor: Releases the resource.
Because stack objects are destroyed automatically, RAII guarantees no resource leaks, even if exceptions are thrown.
Smart Pointers (Modern C++)¶
Rule: Never use new and delete (and definitely never malloc/free). Use smart pointers from <memory>.
std::unique_ptr¶
The default choice. Represents exclusive ownership.
- Usage: Use
std::make_unique<T>()to create. - Transfer: Can be moved (
std::move), but not copied.
std::shared_ptr¶
Use only when ownership is truly shared (e.g., a node in a graph referenced by multiple parents).
- Usage: Use
std::make_shared<T>(). - Overhead: Keeps a reference count.
std::weak_ptr¶
A non-owning observer for shared_ptr. Breaks cyclic references (e.g., Parent <-> Child).
Raw Pointers (*) in Modern C++¶
Raw pointers are not dead, but their role has changed.
- Owning: std::unique_ptr / std::shared_ptr
- Non-owning (Observer): Raw pointer T* or Reference T&.
If a function just needs to use an object, pass it by reference or raw pointer. Do not pass smart pointers unless you are manipulating ownership.
Move Semantics (C++11)¶
Move semantics allow resources to be transferred rather than copied. This is a massive performance optimization.
Lvalues vs. Rvalues¶
- Lvalue: Has a name and an address (e.g.,
x). - Rvalue: Temporary value (e.g.,
10,x + y, return value of a function).
std::move¶
Casts an lvalue to an rvalue, enabling the move constructor/assignment.
Deep Copy vs. Shallow Copy¶
- Shallow Copy: Copies pointer values. Both objects point to the same memory. (Dangerous if not handled carefully).
- Deep Copy: Allocates new memory and copies the actual data. (Safer but slower).
Smart pointers and STL containers handle this correctly automatically.
Best Practices¶
- Prefer Stack: Use stack variables whenever possible.
- Use
std::unique_ptr: By default for heap allocation. - Use
std::shared_ptr: Only when ownership is truly shared. - Avoid
new/delete: They are error-prone.