C++ Cheatsheet - Syntax, STL Containers & Pointers
This reference is for C++ developers writing applications, games, or libraries, organized around modern C++ — where smart pointers, the STL, and lambdas drastically reduce the manual-lifetime bugs C++ is famous for. It covers classes and objects with their access control, the go-to STL containers and when each fits, smart pointers vs raw new/delete, lambda captures, function and class templates, and exceptions. Unlike a flat C++ feature list, entries are grouped by the construct you are writing and flag RAII-versus-manual-ownership choices. After reading you should be able to reach for std::vector/std::map instead of raw arrays, prefer unique_ptr over naked new, and write a templated function or lambda capture-free of leaks.
Typical Use Case
For developers moving from knowing how to new and delete to writing modern, safe C++ while keeping the performance edge. This sheet runs along seven lines: classes and objects, STL containers, smart pointers, lambdas, templates, exception handling, and modern language features. It replaces manual memory management with the unique_ptr / shared_ptr / weak_ptr trio and explains when exclusive ownership is right, when reference counting is needed, and why observers should hold weak references to break cycles. The container section gives a selection guide across vector, list, map, unordered_map, set, stack and queue, and the sheet closes on auto, decltype, constexpr, optional, variant and structured bindings — features that remove boilerplate immediately.
Classes & Objects 8
class MyClass { public: MyClass(); ~MyClass(); };struct MyStruct { int x; int y; };MyClass obj;MyClass *obj = new MyClass();delete obj;class Derived : public Base { };virtual void func() override;class Abstract { virtual void func() = 0; };STL Containers 8
std::vector<int> v = {1, 2, 3};v.push_back(4); v.size(); v[0];std::list<int> l;std::map<std::string, int> m;std::unordered_map<std::string, int> m;std::set<int> s;std::stack<int> st; std::queue<int> q;std::pair<int, std::string> p = {1, "one"};Smart Pointers 5
std::unique_ptr<int> p = std::make_unique<int>(10);std::shared_ptr<int> p = std::make_shared<int>(10);std::weak_ptr<int> wp = sp;if (auto locked = wp.lock()) { }std::unique_ptr<int> p2 = std::move(p);Lambda Expressions 6
auto fn = []() { return 42; };int x = 10; auto fn = [x]() { return x; };auto fn = [&x]() { x++; };auto fn = [=]() { return x; };auto fn = [&]() { x++; };auto fn = [](int a, int b) { return a + b; };Templates 4
template <typename T> T add(T a, T b) { return a + b; }template <typename T> class Stack { };template <> void func<int>(int x) { }std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });Exception Handling 5
try { } catch (const std::exception &e) { }throw std::runtime_error("error");catch (...) { }noexceptstd::terminate();Modern C++ Features 8
auto x = 42;decltype(x) y = 10;constexpr int MAX = 100;std::optional<int> opt = 42;std::variant<int, std::string> v = 42;std::any a = 42;for (auto &[key, value] : map) { }std::string_view sv = "hello";Parameter matrix
| Parameter | Effect | Example |
|---|---|---|
class MyClass | Class definition; members are private by default | class MyClass { public: MyClass(); ~MyClass(); }; |
struct MyStruct | Struct; members are public by default | struct MyStruct { int x; int y; }; |
MyClass obj; | Create an object on the stack; it is destroyed automatically at the end of scope | MyClass obj; |
new MyClass() | Create an object on the heap; must be freed in a pair | MyClass *obj = new MyClass(); |
delete obj; | Free a heap object; null the pointer to avoid dangling | delete obj; |
class Derived : public Base | Public inheritance | class Derived : public Base { }; |
virtual void func() override; | Override a base-class virtual function and mark it for validation | virtual void func() override; |
virtual void func() = 0; | Pure virtual function, making the class abstract | class Abstract { virtual void func() = 0; }; |
std::vector<int> | Dynamic array with contiguous memory, O(1) random access | std::vector<int> v = {1, 2, 3}; |
v.push_back(4) / v.size() / v[0] | vector tail append, length, and index access | v.push_back(4); v.size(); v[0]; |
std::list<int> | Doubly linked list; insertion/removal in the middle does not move elements | std::list<int> l; |
std::map<K, V> | Ordered map (red-black tree), keys stored in order | std::map<std::string, int> m; |
std::unordered_map<K, V> | Hash-based mapping: lookups are faster on average, but iteration order is arbitrary. | std::unordered_map<std::string, int> m; |
std::set<T> | An ordered collection that keeps only distinct values. | std::set<int> s; |
std::stack<T> / std::queue<T> | Adapters that give you LIFO and FIFO semantics. | std::stack<int> st; std::queue<int> q; |
std::pair<A, B> | A two-element tuple, usually consumed with structured bindings. | std::pair<int, std::string> p = {1, "one"}; |
std::make_unique<T>(v) | Build an owning unique_ptr; this is the recommended way to create one. | std::unique_ptr<int> p = std::make_unique<int>(10); |
std::make_shared<T>(v) | Build a shared pointer in a single allocation, which is cheaper than two. | std::shared_ptr<int> p = std::make_shared<int>(10); |
std::weak_ptr<T> wp = sp; | A non-owning reference that does not touch the use count. | std::weak_ptr<int> wp = sp; |
if (auto locked = wp.lock()) | Promote the weak reference to a strong one for this block; a null result means the object has already been destroyed. | if (auto locked = wp.lock()) { } |
std::move(p) | Transfer ownership, leaving the source pointer null. | std::unique_ptr<int> p2 = std::move(p); |
[]() { return 42; } | A lambda that captures nothing. | auto fn = []() { return 42; }; |
[x] / [&x] | Capture one variable, either by value or by reference. | auto fn = [&x]() { x++; }; |
[=] / [&] | Capture every outer variable, either by value or by reference. | auto fn = [&]() { x++; }; |
template <typename T> T add(T, T) | Function template: one instantiation per argument type. | template <typename T> T add(T a, T b) { return a + b; } |
template <typename T> class Stack | Class template. | template <typename T> class Stack { }; |
template <> ... explicit specialization | Hand a specific type its own dedicated implementation. | template <> void func<int>(int x) { } |
std::sort(begin, end, cmp) | Sort a range, with an optional custom comparator. | std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); |
try / catch (const std::exception &e) | Catch a standard exception and read its message. | try { } catch (const std::exception &e) { } |
throw std::runtime_error("error") | Raise an exception carrying a message. | throw std::runtime_error("error"); |
catch (...) | Catch everything, as a last-resort branch. | catch (...) { } |
noexcept | Declare that the function will not throw. | noexcept |
auto x = 42; | Let the initialiser decide the type. | auto x = 42; |
decltype(x) y = 10; | Declare a new variable using the type of an existing expression. | decltype(x) y = 10; |
constexpr int MAX = 100; | A constant fixed at compile time. | constexpr int MAX = 100; |
std::optional<int> | A value that may or may not be there — the safe replacement for a bare pointer standing in for a maybe. | std::optional<int> opt = 42; |
std::variant<int, std::string> | A union over alternative types, fixed at compile time. | std::variant<int, std::string> v = 42; |
std::any a = 42; | Holds a value of any type, at the cost of checking the type at runtime. | std::any a = 42; |
for (auto &[key, value] : map) | Structured bindings: key and value are usable directly. | for (auto &[key, value] : map) { } |
std::string_view sv = "hello" | A non-owning view of string characters, so no copy and no allocation. | std::string_view sv = "hello"; |
Common pitfalls
SymptomThe program occasionally crashes or reads garbage after accessing a pointer following delete.
CauseThe pointer was not nulled after freeing the heap object, creating a dangling pointer; even more insidious is the same memory being freed twice through two pointers.
FixPrefer std::unique_ptr to take ownership; if you must manage it manually, null the pointer immediately after freeing and ensure every new has exactly one matching delete.
SymptomTwo objects hold shared_ptrs to each other, and the program leaks at exit with both still alive.
CauseA shared_ptr cycle keeps the reference count from ever reaching zero, so the objects can never be destroyed.
FixChange one side to std::weak_ptr and let the observer hold a weak reference; on access, temporarily promote it with if (auto locked = wp.lock()).
SymptomDeleting a derived object through a base-class pointer skips the destructor, causing a resource leak.
CauseThe base-class destructor is not virtual, so delete on a base pointer does not run the derived class's destruction logic.
FixDeclare the destructor virtual on any class you intend to manage polymorphically through a base class.
SymptomFrequent push_back in a loop causes performance to plummet suddenly.
CauseGrowing a vector requires reallocating and copying/moving all elements; without reserved capacity, every growth has extra overhead.
FixWhen the size is known, reserve capacity up front, or switch to a container like std::list where insertion/removal in the middle does not move elements.
SymptomConcatenating the same string extensively makes the program slower and slower.
CauseEach + constructs a new string and copies the existing content, so complexity accumulates with length.
FixSwitch to std::string_view to pass read-only slices without copying, and when concatenation is needed, build it in one place instead of repeatedly adding inside a loop.
SymptomA change to an outer variable inside a Lambda does not take effect, or the captured variable has already been destroyed when the Lambda is called.
CauseCapture by value gets a copy from that moment, so later outer changes are invisible; capture by reference can leave a dangling reference after the object is destroyed.
FixClarify the semantics you need: for a snapshot use [x], for live linkage use [&x] and ensure the captured object outlives the Lambda.
SymptomA polymorphic call does not reach the subclass method and falls back to the base-class implementation.
CauseThe virtual function is hidden rather than overridden (the derived class has a same-named function with no override and a different signature), or the call goes through a non-polymorphic path (pass-by-value, template deduction).
FixAdd override at the override site so the compiler validates the signature; call through a base-class reference or pointer to trigger dynamic dispatch.
Troubleshooting
1Confirm exactly where the exception is thrown from
try { } catch (const std::exception &e) { }e.what() gives the information carried by the exception, which you can use to identify which standard exception it is.
2Observe whether the object a weak reference points to has been destroyed
if (auto locked = wp.lock()) { }A successful lock() means the object is still alive; an empty return means the reference count hit zero, so do not dereference it again.
3Verify whether ownership of a shared pointer has been transferred
std::unique_ptr<int> p2 = std::move(p);After the transfer, p is in a null state; using p again is a null-pointer dereference.
4Print the container contents and length to confirm data correctness
v.push_back(4); v.size(); v[0]First confirm the write and read paths are correct, then suspect the sorting or comparison logic.
5Verify sorting behavior with a custom comparison function
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });The comparator must form a strict weak ordering; returning a > b yields descending order.
6Confirm a captured variable outlives the lambda
auto fn = [&x]() { x++; };A by-reference capture dangles as soon as the enclosing scope ends, which is easy to hit when the lambda is handed to an async task or stored somewhere long-lived.
Command Examples
RAII: make_unique owns the lifetime, move transfers ownership
auto p = std::make_unique<int>(10);
std::unique_ptr<int> p2 = std::move(p);
if (!p) {
// p owns nothing now, so its destructor cannot double-free
}A unique_ptr deletes on scope exit, including during unwinding, and is null after std::move. Returning one from a function cannot copy — rely on the move or on NRVO doing it implicitly.
shared_ptr plus weak_ptr to break reference cycles
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock()) {
// locked is a live shared_ptr, so sp cannot reach zero inside this block
}Two shared_ptrs pointing at each other keep the count above zero forever, leaking the node and everything it owns. A weak_ptr does not count, and lock() yields a null pointer once the object is gone.
Value capture to avoid dangling, reference capture to mutate
int x = 10;
auto byValue = [x]() { return x; };
auto byRef = [&x]() { x++; };
byRef();
int y = byValue();Value capture copies x into the closure, so later changes are invisible to it. Reference capture stores an address: once the captured local goes out of scope, invoking the closure is a dangling reference.
Structured bindings over a map, with optional for absent values
std::map<std::string, int> m = {{"a", 1}};
for (auto &[key, value] : m) {
value += 1;
}
std::optional<int> opt = find(3);
if (opt.has_value()) {
use(*opt);
}auto & modifies the map in place while auto copies and discards the change. optional must be dereferenced before arithmetic — the compile error replaces the silent mistake of using an unchecked value.
Exceptions: catch by type and keep the message
try {
throw std::runtime_error("error");
} catch (const std::exception &e) {
log(e.what());
} catch (...) {
log("unknown");
}Catching a const reference to the base is what lets you reach what(). Without a matching handler the exception propagates to main, std::terminate fires, and the program exits with no readable stack.
Common Pitfalls
- Dangling references usually come from reference captures and temporaries: returning [x] is only safe with a value capture, and holding a pointer or reference into a vector across a push_back that reallocates leaves it dangling.
- Iterators and references are invalidated by container changes: incrementing after erasing the current element is undefined behavior, and push_back inside a range-for desynchronizes the traversal from storage.
- Raw new leaks on exceptions: if construction throws partway, the matching delete never runs. Prefer unique_ptr for members and reach for shared_ptr only when ownership is genuinely shared.
- Two shared_ptrs holding each other create a cycle whose count never reaches zero — valgrind reports "still reachable" while memory keeps growing. One link in the cycle must be a weak_ptr.
- Deleting a derived object through a base pointer without a virtual base destructor is undefined behavior: derived members are not destroyed, resources leak, and some runtimes crash outright.
- Mixing delete with delete[], double-freeing, and deleting a stack object all corrupt the heap. The crash then surfaces far from the offending line, and an invalid-free report from valgrind is the only real clue.
Tips
- Prefer smart pointers over manual new/delete.
- STL container choice: vector for random access, list for frequent inserts/deletes, unordered_map for key lookups.
- Lambdas suit short callbacks; use a plain function or functor for complex logic.
FAQ
Why does a shared_ptr reference cycle leak memory?
shared_ptr manages lifetime by reference count: an object dies when the count hits zero. But counts move in pairs — if A holds a shared_ptr to B and B holds a shared_ptr to A, each keeps the other alive, so even after every outside shared_ptr is destroyed both objects remain and their heap memory is never reclaimed. Break the cycle by making one side a std::weak_ptr<int> wp = sp, which does not increment the count, and promote it on demand with if (auto locked = wp.lock()) { }. Then the objects can actually be destroyed.
What actually differs between capturing by value and by reference in a lambda?
[x] copies x at the point the lambda is defined, so writes inside affect only that copy and the outer x is untouched. [&x] captures a reference to the same object, so [&x]() { x++ } really does increment the outer x — but if the lambda outlives x (stored in a container, returned, run asynchronously) you are left referencing a dead object. Rule of thumb: use by-reference for short synchronous callbacks to avoid copies, and prefer by-value for escaping or lifetime-uncertain callbacks, using [&] when you need to write and [=] when you only read.
Which hand-written loops does C++17 structured binding replace?
Iterating a std::map used to require for (const auto &p : m) { auto key = p.first; auto value = p.second; }. In C++17 you write for (auto &[key, value] : map) { } and get both variables directly; the reference form also allows in-place modification. Pairs work the same way: for (const auto &[id, name] : users). The gain is not just brevity — destructuring becomes expressible in the syntax itself, so the intent reads clearly. Keep in mind auto & binds a reference, so writes hit the container; use const auto & for read-only traversal.
When should I pick unordered_map over map?
The difference comes down to the underlying structure and its cost. std::map is a red-black tree: keys stay ordered, and lookup, insert and delete are all O(log n) while ordered traversal comes for free — it is the right choice whenever you need the largest key or sorted output. std::unordered_map is a hash table with average O(1) operations and no ordering guarantee; iteration order follows the hash distribution, which makes it faster for pure lookups. So ask first: does this mapping need ordered traversal or range queries? If yes, take map. If no and keys are numerous and lookups frequent, take unordered_map, keeping in mind hashing an object key erodes the advantage.
What problems do pure virtual functions, override and noexcept each solve?
virtual void func() = 0 declares a pure virtual: the class becomes abstract and non-instantiable, and every derived class must implement it, so the interface contract is enforced by the compiler rather than by convention (paired with a virtual destructor so deleting through a base pointer stays safe). override marks a rewrite, and if the signature ever drifts from the base the compiler errors immediately — that catches the classic bug where a mistyped parameter silently creates an overload instead. noexcept promises no exception, letting containers such as std::vector take faster move paths when reallocating; breaking that promise calls std::terminate().
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us