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.

Languages·44 commands·Last updated 2026-07-21
cppstlOOP

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(); };
Class definition
struct MyStruct { int x; int y; };
Struct (public by default)
MyClass obj;
Stack object
MyClass *obj = new MyClass();
Heap object
delete obj;
Delete an object
class Derived : public Base { };
Inheritance
virtual void func() override;
Override a virtual function
class Abstract { virtual void func() = 0; };
Pure virtual function (abstract class)

STL Containers 8

std::vector<int> v = {1, 2, 3};
Dynamic array
v.push_back(4); v.size(); v[0];
vector operations
std::list<int> l;
Doubly linked list
std::map<std::string, int> m;
Ordered map (red-black tree)
std::unordered_map<std::string, int> m;
Unordered map (hash table)
std::set<int> s;
Ordered set
std::stack<int> st; std::queue<int> q;
Stack and queue
std::pair<int, std::string> p = {1, "one"};
Key-value pair

Smart Pointers 5

std::unique_ptr<int> p = std::make_unique<int>(10);
Exclusive ownership pointer
std::shared_ptr<int> p = std::make_shared<int>(10);
Shared pointer (ref-counted)
std::weak_ptr<int> wp = sp;
Weak reference (no ref count)
if (auto locked = wp.lock()) { }
Weak to strong reference
std::unique_ptr<int> p2 = std::move(p);
Transfer ownership

Lambda Expressions 6

auto fn = []() { return 42; };
Non-capturing lambda
int x = 10; auto fn = [x]() { return x; };
Capture by value
auto fn = [&x]() { x++; };
Capture by reference
auto fn = [=]() { return x; };
Capture all by value
auto fn = [&]() { x++; };
Capture all by reference
auto fn = [](int a, int b) { return a + b; };
Lambda with parameters

Templates 4

template <typename T> T add(T a, T b) { return a + b; }
Function template
template <typename T> class Stack { };
Class template
template <> void func<int>(int x) { }
Template specialization
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
Custom sort

Exception Handling 5

try { } catch (const std::exception &e) { }
Catch an exception
throw std::runtime_error("error");
Throw an exception
catch (...) { }
Catch all exceptions
noexcept
Declare no-throw
std::terminate();
Terminate the program

Modern C++ Features 8

auto x = 42;
Type deduction
decltype(x) y = 10;
Declared type
constexpr int MAX = 100;
Compile-time constant
std::optional<int> opt = 42;
Optional value
std::variant<int, std::string> v = 42;
Variant type
std::any a = 42;
Any type
for (auto &[key, value] : map) { }
Structured binding (C++17)
std::string_view sv = "hello";
String view (C++17)

Parameter matrix

ParameterEffectExample
class MyClassClass definition; members are private by defaultclass MyClass { public: MyClass(); ~MyClass(); };
struct MyStructStruct; members are public by defaultstruct MyStruct { int x; int y; };
MyClass obj;Create an object on the stack; it is destroyed automatically at the end of scopeMyClass obj;
new MyClass()Create an object on the heap; must be freed in a pairMyClass *obj = new MyClass();
delete obj;Free a heap object; null the pointer to avoid danglingdelete obj;
class Derived : public BasePublic inheritanceclass Derived : public Base { };
virtual void func() override;Override a base-class virtual function and mark it for validationvirtual void func() override;
virtual void func() = 0;Pure virtual function, making the class abstractclass Abstract { virtual void func() = 0; };
std::vector<int>Dynamic array with contiguous memory, O(1) random accessstd::vector<int> v = {1, 2, 3};
v.push_back(4) / v.size() / v[0]vector tail append, length, and index accessv.push_back(4); v.size(); v[0];
std::list<int>Doubly linked list; insertion/removal in the middle does not move elementsstd::list<int> l;
std::map<K, V>Ordered map (red-black tree), keys stored in orderstd::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 StackClass template.template <typename T> class Stack { };
template <> ... explicit specializationHand 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 (...) { }
noexceptDeclare 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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