C++ Programming · Lesson 8 of 9 · Updated ·
Memory in C++ — new/delete, RAII, Smart Pointers, Copy vs Move and Exceptions
In C you malloc and free by hand. Modern C++ makes objects clean up after themselves, so leaks and double frees mostly disappear.
1. new and delete
int *p = new int(42); // one int on the heap
int *arr = new int[100]; // an array
delete p; // free one
delete[] arr; // free an array (note the [])
Forgetting delete leaks memory; using delete instead of delete[] is undefined behaviour. In modern C++ you should rarely write new/delete yourself.
2. RAII: tie resources to object lifetime
RAII (Resource Acquisition Is Initialisation): acquire a resource in a constructor, release it in the destructor. Since destructors run automatically — even when an exception is thrown — the resource is always released. string, vector, fstream, lock_guard and smart pointers all work this way.
3. Smart pointers
#include <iostream>
#include <memory>
#include <string>
using namespace std;
struct Node {
string name;
explicit Node(string n) : name(n) { cout << "create " << name << "\n"; }
~Node() { cout << "destroy " << name << "\n"; }
};
int main() {
{
unique_ptr<Node> a = make_unique<Node>("A"); // single owner
unique_ptr<Node> b = move(a); // ownership moves to b
cout << (a == nullptr) << " " << b->name << "\n";
} // b goes out of scope → A destroyed
shared_ptr<Node> s1 = make_shared<Node>("S");
{
shared_ptr<Node> s2 = s1; // two owners
cout << s1.use_count() << "\n";
}
cout << s1.use_count() << "\n";
return 0;
} // last owner gone → S destroyed
create A
1 A
destroy A
create S
2
1
destroy S
| Smart pointer | Owners | Use for |
|---|---|---|
unique_ptr | exactly one (can be moved, not copied) | the default choice |
shared_ptr | many (reference counted) | genuinely shared ownership |
weak_ptr | observes a shared_ptr without owning | breaking reference cycles |
4. Copy vs move, and the rule of three/five
Copying a big object (a vector with a million items) is expensive. C++11 added move: transfer the internals instead of copying them.
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
int main() {
vector<int> big(1000000, 7);
vector<int> copyOf = big; // copies a million ints
vector<int> movedTo = move(big); // steals the buffer — very fast
cout << copyOf.size() << " " << movedTo.size() << " " << big.size() << "\n";
return 0;
}
1000000 1000000 0
(After a move, the source is valid but unspecified — usually empty. Don't rely on its contents.)
Rule of three / five: if your class manages a raw resource and needs a custom destructor, it probably also needs a custom copy constructor and copy assignment (three), plus move constructor and move assignment (five). Rule of zero: better still, use vector, string and smart pointers so you need none of them.
5. Exceptions
#include <iostream>
#include <stdexcept>
using namespace std;
double divide(double a, double b) {
if (b == 0) throw invalid_argument("division by zero");
return a / b;
}
int main() {
try {
cout << divide(10, 4) << "\n";
cout << divide(1, 0) << "\n";
cout << "not reached\n";
} catch (const invalid_argument &e) {
cout << "Error: " << e.what() << "\n";
}
cout << "continues\n";
return 0;
}
2.5
Error: division by zero
continues
Catch by const reference. Standard exceptions include invalid_argument, out_of_range (e.g. vector::at), runtime_error, bad_alloc.
Common mistakes
- Raw
newwithout a matchingdelete(usemake_uniqueinstead). - Copying
unique_ptr(compile error — usemove). shared_ptrcycles (A owns B, B owns A) → never freed; useweak_ptrfor one side.
Quick recap
- Avoid raw new/delete; RAII cleans up in destructors.
- unique_ptr (default), shared_ptr (shared), weak_ptr (no ownership).
- Move transfers resources cheaply; follow the rule of zero.
- throw / try / catch (by const reference) for errors.