Last 30 Days
No notifications
C++ gives you direct heap access (new/delete) — but in modern code you almost never write either by hand. Smart pointers in own the resource and free it automatically.
#include <memory>auto p = make_unique<int>(42); // unique_ptr — sole owner
cout << *p; // 42
// no delete needed — frees automatically when 'p' goes out of scope
| Type | Ownership | Cost |
unique_ptr | Sole owner — non-copyable, only movable | Zero overhead |
shared_ptr | Shared owners (ref-counted) — copyable | Atomic ref count |
weak_ptr | Non-owning observer — breaks cycles | Used with shared_ptr |
// ❌ Old C++ — exception-unsafe, easy to leak
Widget* w = new Widget();
mayThrow();
delete w; // never runs if mayThrow throws// ✅ Modern C++
auto w = make_unique<Widget>();
mayThrow(); // even if this throws, ~unique_ptr runs and frees
| Stack | Heap |
| Auto-managed by scope | You own it (or smart pointer does) |
| Tiny (a few MB) | Huge (gigabytes) |
| Super fast | Slower (allocator) |
| Size known at compile time | Size can be runtime |
> Default: put things on the stack. Heap-allocate (via make_unique / make_shared / containers) only when you need: dynamic size, polymorphism, or shared ownership.
Memory management is C++'s most famous footgun. Modern C++ defangs it with RAII, standard containers, and smart pointers. Master them and you'll virtually never new/delete again.
int* n = new int(42); // allocate
int* arr = new int[100]; // allocate array
delete n; // free single
delete[] arr; // free array — NOTE the []Problems:
1. Leak if you forget to delete (or an exception skips it)
2. Double-free if you delete twice
3. Use-after-free if you keep using a deleted pointer
4. Wrong delete form (delete vs delete[]) → undefined behaviour
Avoid new/delete in modern code. Use make_unique/make_shared and standard containers.
> *Resource Acquisition Is Initialisation* — own resources in objects, release them in destructors.
std::vector, std::string, std::ifstream, std::unique_ptr — all RAII wrappers around raw resources.
std::unique_ptr — The DefaultSole, exclusive ownership. Cannot be copied (would mean two owners) — but can be moved.
#include <memory>auto p = make_unique<Widget>(arg1, arg2); // construct on heap
p->doStuff();
(*p).field = 42;
// auto q = p; // ❌ compile error — can't copy
auto q = std::move(p); // ✅ transfer ownership; p becomes nullptr
unique_ptr<Shape> makeShape(string kind) {
if (kind == "circle") return make_unique<Circle>(5);
if (kind == "rectangle") return make_unique<Rectangle>(3, 4);
return nullptr;
}Returning by value moves the unique_ptr out — no copy, no leak.
vector<unique_ptr<Animal>> zoo;
zoo.push_back(make_unique<Dog>());
zoo.push_back(make_unique<Cat>());for (const auto& a : zoo) a->speak();
// All animals freed when 'zoo' is destroyed
auto buf = make_unique<int[]>(100); // 100 ints, zero-initialised
buf[0] = 42;
// (Usually prefer std::vector — more features.)std::shared_ptr — Shared OwnershipMultiple owners, reference-counted. The object dies when the last shared_ptr to it goes away.
auto a = make_shared<Image>("photo.png");
auto b = a; // ref count = 2
{
auto c = a; // ref count = 3
} // c destroyed → ref count = 2
// when a and b also die → ref count = 0 → Image deletedunique_ptr)unique_ptr. Reach for shared_ptr only when ownership is genuinely shared.weak_ptrTwo shared_ptrs that point to each other never reach refcount 0 — memory leak.
struct Node {
shared_ptr<Node> next;
};auto a = make_shared<Node>();
auto b = make_shared<Node>();
a->next = b;
b->next = a; // ❌ cycle — never freed
Break the cycle with weak_ptr (non-owning observer):
struct Node {
shared_ptr<Node> next;
weak_ptr<Node> prev; // doesn't keep prev alive
};To use a weak_ptr, lock it back to a shared_ptr (returns null if already deleted):
if (auto p = wptr.lock()) {
p->doSomething();
}Raw T* is fine for non-owning parameters:
void inspect(const Widget* w); // optional — caller still owns
void log(const Widget& w); // required — caller still ownsunique_ptr<Widget> w = ...;
inspect(w.get()); // pass raw pointer
| Want to express | Use | |||
| "I own it; only one owner" | unique_ptr | |||
| "I own it; many owners share" | shared_ptr | |||
| "I observe but don't own" | T* or T& (or weak_ptr for shared cases) | |||
| "It's a fixed-size array" | std::array or stack array | |||
| "It's a dynamic array" | std::vector | Common Bugs You'll Avoid | Old C++ Bug | Modern Fix |
Forgot to delete | unique_ptr/shared_ptr | |||
delete twice | unique_ptr can't be copied; shared_ptr is ref-counted | |||
Used after delete | RAII destroys at scope end, no dangling | |||
Wrong delete vs delete[] | Use vector or unique_ptr | |||
| Leak on exception | Smart pointers' dtors run during stack unwinding |
unique_ptr accepts a custom deleter — handy for C resources:
unique_ptr<FILE, decltype(&fclose)> file(fopen("a.txt", "r"), &fclose);
// auto-closes when 'file' goes out of scope| Need | Code |
| Make on heap (sole owner) | auto p = make_unique |
| Make on heap (shared) | auto p = make_shared |
| Move ownership | auto q = std::move(p); |
| Get raw pointer | p.get() |
| Reset / replace | p.reset(new T) or p.reset() |
| Polymorphic container | vector |
| Break cycle | weak_ptr |
| Dynamic array | std::vector (preferred) |
| Fixed array | std::array |
You now write C++ that doesn't leak, doesn't double-free, and doesn't crash on exceptions — without ever calling delete. That's the modern C++ promise.
Next: templates — generic code that works for any type.