Modern C++
C++11 through C++20 changed the language's culture. Smart pointers, move semantics, and type inference let you write memory-safe, fast code that reads almost like a managed language.
Smart Pointers
Smart pointers are RAII wrappers around heap memory that free it automatically. They make raw new / delete obsolete in modern code, eliminating leaks and dangling pointers by construction.
#include <memory> // unique_ptr — sole owner, freed automatically. Zero overhead. std::unique_ptr<int> p = std::make_unique<int>(42); *p; // 42 // no delete needed — freed when p goes out of scope // shared_ptr — reference-counted, freed when the last owner dies std::shared_ptr<int> a = std::make_shared<int>(10); std::shared_ptr<int> b = a; // both own it, count = 2 a.use_count(); // 2 // freed only when BOTH a and b are gone
| Smart pointer | Ownership | Use when |
|---|---|---|
unique_ptr | Exactly one owner | Default — cheapest, most common |
shared_ptr | Shared, reference-counted | Multiple owners, unclear lifetime |
weak_ptr | Non-owning observer | Break shared_ptr reference cycles |
new / delete. Use make_unique by default, make_shared only when ownership is genuinely shared. This is how modern C++ gets memory safety without a garbage collector.Move Semantics
Move semantics (C++11) let you transfer a resource out of an object instead of copying it. Returning a big vector from a function no longer copies the whole thing — it moves the internal buffer, which is nearly free.
#include <utility> std::vector<int> a = {1, 2, 3, 4, 5}; // copy — duplicates all elements (expensive) std::vector<int> b = a; // move — steals a's buffer, a is left empty (cheap) std::vector<int> c = std::move(a); a.size(); // 0 — a was moved-from, do not rely on its value c.size(); // 5 — c now owns the data // returning a local by value moves automatically (or is elided) std::vector<int> build() { std::vector<int> v = {1, 2, 3}; return v; // moved out, not copied }
std::move(a), a is in a valid but unspecified state — you may assign to it or destroy it, but don't read its value. Moving is a one-way transfer, not a copy.auto, Range-for & Structured Bindings
Modern syntax cuts the verbosity C++ was famous for. Type inference, clean iteration, and unpacking make the language far more readable than its 1990s reputation suggests.
// auto — no more typing out long iterator types std::map<std::string, int> scores = {{"a", 1}, {"b", 2}}; auto it = scores.begin(); // instead of std::map<...>::iterator // range-for + structured bindings (C++17) — unpack key/value for (const auto& [key, value] : scores) { std::cout << key << " = " << value << "\n"; } // structured bindings on a pair auto [lo, hi] = std::minmax({3, 1, 4, 1, 5}); // lo = 1, hi = 5 // nullptr, constexpr, and more all landed in modern C++ constexpr int SIZE = 100; // computed at compile time
Common Pitfalls
Manual memory management is error-prone. Use make_unique / make_shared so cleanup is automatic and leaks are impossible.
After std::move(x), x holds an unspecified value. Assign a new value before using it again.
Two objects holding shared_ptrs to each other never reach count zero, so neither is freed. Break the cycle with a weak_ptr.
vector, string, and smart pointers already do memory management correctly. Lean on them instead of hand-rolling buffers and pointers.