Reading — step 1 of 7
Learn
Modern C++ replaces raw new/delete with smart pointers — wrappers that own a heap object and automatically delete it when they go out of scope. This is RAII (Resource Acquisition Is Initialization) applied to memory management.
std::unique_ptr — sole ownership
#include <memory>
std::unique_ptr<int> p = std::make_unique<int>(42);
std::cout << *p; // 42
// when p goes out of scope, the int is automatically deleted
unique_ptr<T> represents single ownership of a heap object. When it dies, it deletes. You never write delete.
Move-only — can't copy
std::unique_ptr<int> p1 = std::make_unique<int>(5);
// std::unique_ptr<int> p2 = p1; // ✗ compile error — can't copy
std::unique_ptr<int> p2 = std::move(p1); // ✓ ownership moved
// p1 is now empty (nullptr); p2 owns the int
The move semantics enforce single-ownership at compile time. If you have a unique_ptr, you KNOW no one else owns the same memory.
Returning from functions
std::unique_ptr<int> makeInt() {
return std::make_unique<int>(42); // returned by move
}
auto p = makeInt(); // p owns the int
std::shared_ptr — shared ownership
When multiple parts of your code need to OWN the same object (graph nodes, observer patterns):
#include <memory>
auto p1 = std::make_shared<std::string>("hello");
auto p2 = p1; // ✓ copy — increments ref count
auto p3 = p1; // ref count is now 3
std::cout << p1.use_count(); // 3
// when all three go out of scope, the string is freed
Reference-counted ownership. Each copy bumps the count; each destruction decrements. When count hits zero, delete runs.
Trade-off: shared_ptr has overhead (the control block holding the count) and atomic operations on copy/destroy. Use unique_ptr unless you genuinely need shared ownership.
std::weak_ptr — non-owning observer
Used to break reference cycles in shared_ptr graphs:
auto p = std::make_shared<int>(42);
std::weak_ptr<int> wp = p; // doesn't increment ref count
if (auto locked = wp.lock()) { // try to obtain a shared_ptr
std::cout << *locked; // ✓ p is still alive
}
p.reset(); // last shared_ptr gone
if (auto locked = wp.lock()) {
// doesn't run — int already deleted
}
Weak pointers are how you observe a shared object without keeping it alive.
Custom deleters
For non-default cleanup (closing files, releasing locks):
auto fileDeleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(fileDeleter)> file(fopen("data.txt", "r"), fileDeleter);
// file auto-closes when out of scope
When to use which
unique_ptr— default. Single owner, clear lifecycle. Cheaper than shared_ptr.shared_ptr— when multiple owners genuinely exist. Often signals a design that could be simpler.weak_ptr— observers, breaking shared_ptr cycles. Caches that shouldn't keep their entries alive.- Raw pointers (
T*) — non-owning observation, optional/nullable parameters. Document with comments that you don't own them.
Common mistakes
- Mixing
newwith smart pointers:unique_ptr<T> p(new T)works butmake_uniqueis preferred (exception-safe, less typing). - Storing the same raw pointer in multiple smart pointers → double-free. Always use the same smart-pointer instance, not raw new wrapped twice.
- shared_ptr cycles — A holds shared_ptr<B>, B holds shared_ptr<A> → both leak. Use weak_ptr for one direction.
- Returning a raw pointer to a smart_ptr-managed object — risk of dangling once the smart_ptr dies. Document ownership.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…