DS DevShelfHub Projects · AI tools
Cheatsheets / C++
Cheatsheet · Languages

C++: Smart Pointers, STL, Concurrency Reference Guide

By DevShelfHub

Modern C++ (17/20/23) — references, smart pointers, RAII, templates, STL containers, algorithms, move semantics, concepts, ranges, concurrency.

124 items 9 min RAII Templates STL

Start hereQuick start · 6 you’ll reach for daily

Unique ownauto p = std::make_unique<T>(…)
Movestd::move(x)
Containerstd::vector<int> v{1,2,3};
Algorithmstd::ranges::sort(v)
Concepttemplate <std::integral T>
Threadstd::jthread t([]{ … });

Target versions · paceVersions

Targets: C++23 (latest) · C++20 (widely deployed) g++ 13+ · clang++ 17+ · MSVC 19.38+ CMake ≥ 3.25

Default to C++20 for portability, opt into C++23 features when your toolchain supports them. The big modern shifts: RAII + smart pointers replace manual new/delete; move semantics replace explicit copies; ranges + concepts (C++20) make generic code readable; modules begin to replace header includes (still gradual adoption). Build systems converged on CMake; vcpkg / Conan handle dependencies.

Install · buildSetup

bash
# Compilers
brew install llvm                       # clang++ (macOS)
sudo apt install g++ clang              # Linux
# Windows: use MSVC via Visual Studio, or w64-mingw / clang via winget

# Build systems
brew install cmake ninja                # the modern default
brew install meson                      # alternative
brew install vcpkg                      # Microsoft's dep manager

# Compile a single file
g++ -std=c++23 -O2 -Wall -Wextra main.cpp -o app

# CMake skeleton
mkdir build && cd build
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..
ninja
./myapp

# Daily commands
ctest --output-on-failure               # run tests via CMake
clang-format -i src/*.cpp               # format
clang-tidy src/*.cpp                    # lint
valgrind ./app                          # leak check (Linux)
asan: g++ -fsanitize=address,undefined  # Address + UB sanitizers

Where things liveCommon headers

<iostream>, <print> (C++23)Streams + std::println.
<string>, <string_view>Owning string / non-owning view.
<vector>, <array>, <deque>, <list>, <forward_list>Sequence containers.
<map>, <unordered_map>, <set>, <unordered_set>Associative containers.
<memory>unique_ptr, shared_ptr, make_*.
<algorithm>, <ranges>Classical algorithms + ranges adaptors.
<numeric>accumulate, iota, reduce, midpoint.
<optional>, <variant>, <expected> (C++23)Nullable, tagged union, value-or-error.
<thread>, <mutex>, <atomic>, <future>Concurrency primitives.
<chrono>Type-safe time. 10ms, std::chrono::steady_clock.
<format>, <span>, <source_location>, <concepts>C++20 additions you’ll use a lot.

Bindings · control flowSyntax basics

int x = 5;Old-style. Still valid.
auto x = 5;Preferred Let the compiler deduce.
const auto& v = compute();Bind by reference to a const result.
constexpr int MAX = 100;Compile-time constant.
constinit, constevalForce compile-time init / compile-time call (C++20).
if (auto v = f(); v > 0) { … }If with initializer (C++17).
switch / for / while / do-whileStandard. switch needs break.
for (auto x : v) / for (auto& x : v)Range-based loop.
[[nodiscard]], [[maybe_unused]], [[deprecated]]Attributes. Compiler warnings.
enum class Role { Admin, Member }Scoped enum. Preferred over old enum.

Ownership made safeReferences & smart pointers

References & pointers

T&, const T&Reference. Bound at creation, can’t be reseated.
T&&Rvalue reference. The "move from me" type.
T*Raw pointer. Use to borrow, never to own.
nullptrType-safe null. NULL is legacy.
std::reference_wrapper<T>Reference that can be reseated and stored in containers.

Smart pointers

auto p = std::make_unique<T>(args)Preferred Sole ownership.
auto p = std::make_shared<T>(args)Reference-counted shared ownership.
std::weak_ptr<T>Observer. Use to break shared_ptr cycles.
std::move(p)Transfer a unique_ptr. Source becomes null.
p.get()Borrow the underlying raw pointer.
No new in modern codeIf you write new, you also need a corresponding delete. Use make-functions.

Worked example

cpp
#include 
#include 
#include 
#include 

struct Connection {
    std::string url;
    Connection(std::string u) : url(std::move(u)) { std::cout << "open " << url << "\n"; }
    ~Connection()                                  { std::cout << "close " << url << "\n"; }
};

// unique_ptr — sole owner, transferred via std::move
std::unique_ptr open_db(const std::string& url) {
    return std::make_unique(url);
}

// Pass by raw pointer/reference when ownership isn't shared
void ping(Connection& c) { std::cout << "ping " << c.url << "\n"; }

int main() {
    auto db = open_db("postgres://localhost");
    ping(*db);                                     // borrow

    // shared_ptr — reference-counted shared ownership
    std::shared_ptr a = std::make_shared("redis://x");
    std::shared_ptr b = a;             // use_count == 2
    std::cout << "uses: " << a.use_count() << "\n";

    // weak_ptr — non-owning observer, breaks cycles
    std::weak_ptr w = a;
    if (auto live = w.lock()) ping(*live);
}
// Connections close in reverse order — deterministic RAII

The Rule of Zero / FiveClasses, RAII, move semantics

struct Foo { int x; };All members public by default. Use for data bags.
class Foo { public: … private: int x_; };All members private by default.
Foo() = default; ~Foo() = default;Explicitly request the compiler-generated version.
Foo(const Foo&) = delete;Disallow copy. Compiler enforces.
Rule of ZeroIf members manage themselves, you don’t write any special members.
Rule of FiveIf you write one of destructor / copy / copy-assign / move / move-assign, you probably need all five.
explicit Foo(int x)Block implicit conversions in single-arg constructors.
virtual ~Base() = default;Always virtual-destructor a polymorphic base.
override / final on methodsCatch typos. Lock down hierarchies.
Foo(Foo&&) noexceptMove constructor. noexcept matters — STL containers prefer it.
Foo& operator=(Foo&&) noexceptMove assignment.
auto x = std::move(y);Cast to rvalue. After move, y is "valid but unspecified".

Compile-time polymorphismTemplates & concepts

template <typename T> T add(T a, T b)Function template. Old form: class T is identical.
template <typename T> struct Box { … };Class template.
template <typename T = int> / non-type paramsDefaults + value parameters.
template <auto N>Non-type template parameter, type deduced.
if constexpr (std::is_integral_v<T>) { … }Compile-time branch.
template <std::integral T>Preferred Concept-constrained template.
requires std::same_as<T, U>Inline requires-clause.
concept Numeric = std::integral<T> || std::floating_point<T>Define a custom concept.
auto f(std::integral auto x)Abbreviated function template.
template <typename… Args>Parameter pack. Expand with Args….
fold expressions: (args + …)Reduce a pack with an operator (C++17).

Worked example

cpp
#include 
#include 
#include 
#include 
#include 

// Constrained generic — C++20 concepts
template 
T max_of(const std::vector& xs) {
    return *std::ranges::max_element(xs);
}

// Auto-deduced parameters via abbreviated function templates
auto sum(std::ranges::range auto&& xs) {
    using T = std::ranges::range_value_t;
    T total{};
    for (auto&& x : xs) total += x;
    return total;
}

// Class template + deduction guide
template 
struct Stack {
    std::vector data;
    void push(T v) { data.push_back(std::move(v)); }
    T pop()        { T v = std::move(data.back()); data.pop_back(); return v; }
};

int main() {
    std::vector v{3, 7, 1, 9, 2};
    std::cout << "max = " << max_of(v) << "\n";
    std::cout << "sum = " << sum(v)    << "\n";

    Stack s{};
    s.push(1); s.push(2); s.push(3);
    std::cout << s.pop() << "\n";
}

Pick the right oneSTL containers

std::vector<T>Contiguous dynamic array. Default choice.
std::array<T, N>Fixed-size, on stack. Replaces C arrays.
std::deque<T>Random-access deque. Cheap push_front + push_back.
std::list<T> / std::forward_list<T>Doubly / singly linked. Rarely the right pick.
std::string / std::string_viewOwned UTF-8 / non-owning view.
std::span<T>Non-owning view of contiguous data. The new "pointer + length".
std::map<K, V> / std::set<K>Ordered associative. Red-black tree.
std::unordered_map / unordered_setHash-based. Faster, no order.
flat_map / flat_set (C++23)Sorted vectors. Cache-friendly.
std::optional<T>Maybe-a-value. has_value() / *opt.
std::variant<A, B, C>Type-safe union. Visit with std::visit.
std::expected<T, E> (C++23)Result-style value-or-error.
std::tuple<…> / std::pair<A, B>Anonymous record. Use structured bindings to unpack.

STL workhorsesAlgorithms & ranges

std::ranges::sort(v) / std::ranges::sort(v, std::greater{})In-place sort. Range version is the modern default.
std::ranges::find(v, x)Linear find. Returns iterator.
std::ranges::find_if(v, pred)Predicate find.
std::ranges::count(v, x) / count_ifCount occurrences.
std::ranges::transform(in, out_begin, fn)Map.
std::accumulate / std::reduceFold. reduce is the parallel-friendly variant.
std::ranges::all_of / any_of / none_ofQuantifiers.
v | std::views::filter(p) | std::views::transform(f)Lazy range pipelines (C++20).
std::views::take(n), drop(n), reverse, zip (C++23), enumerate (C++23)Common adaptors.
std::ranges::to<Vec>(view) (C++23)Materialize a view into a container.

Exceptions · expectedError handling

throw std::runtime_error("…")Throws by value, caught by reference.
try { … } catch (const std::exception& e) { … }Catch by const ref. Always.
noexcept / noexcept(expr)Function won’t throw. Enables move optimizations.
std::optional<T>For "found / not found" without exceptions.
std::expected<T, E>For recoverable failure paths (C++23). Cleaner than exceptions in hot code.
std::error_code / std::system_errorFor OS / library errors that come back as codes.
RAII protects you from leaks during throwsDestructors run during stack unwinding — that’s the whole point.
Never throw from a destructorDuring stack unwind it triggers std::terminate.

Threads · futures · atomicsConcurrency

std::thread t([]{ … })Spawn an OS thread. Must join() or detach() before destructor runs.
std::jthread t([]{ … })Preferred (C++20) Auto-joins. Has built-in stop_token.
std::async(launch::async, fn)Returns a future<T>. Easiest fan-out.
future.get() / wait()Block on a result. Get throws if the work did.
std::mutex m; std::lock_guard lk{m};RAII lock. scoped_lock for multiple.
std::shared_mutex / std::shared_lockReader-writer lock.
std::condition_variable cv; cv.wait(lk, pred);Wait + predicate, immune to spurious wakeups.
std::atomic<int> counter{0};Lock-free atomics. fetch_add, compare_exchange_strong.
std::stop_tokenCooperative cancellation for jthread.
std::execution::parParallel STL: std::sort(std::execution::par, v.begin(), v.end()).

Parallel sum · ~35 linesEnd-to-end · Threaded reduce

Split a million-element vector across hardware_concurrency threads via std::async, sum each chunk, reduce. Pure standard library — no Boost, no OpenMP.

cpp
// C++20. Build:
//   g++ -std=c++20 -O2 -pthread main.cpp -o app

#include 
#include 
#include 
#include 
#include 
#include 
#include 

// Parallel sum using futures: split, sum locally, reduce
long long parallel_sum(const std::vector& v, std::size_t threads) {
    std::vector> parts;
    const auto chunk = v.size() / threads;

    for (std::size_t i = 0; i < threads; ++i) {
        auto begin = v.begin() + i * chunk;
        auto end   = (i + 1 == threads) ? v.end() : begin + chunk;
        parts.push_back(std::async(std::launch::async, [begin, end] {
            return std::accumulate(begin, end, 0LL);
        }));
    }

    long long total = 0;
    for (auto& p : parts) total += p.get();         // wait + accumulate
    return total;
}

int main() {
    std::vector v(1'000'000);
    std::iota(v.begin(), v.end(), 1);               // 1..N

    const auto threads = std::max(1u, std::thread::hardware_concurrency());
    std::cout << "threads = " << threads << "\n";
    std::cout << "sum     = " << parallel_sum(v, threads) << "\n";
}

Best practiceGood to know

Reach for the Rule of Zero. If your class has only members that manage themselves (smart pointers, containers, strings), you write zero special members and the compiler gives you correct copy / move / destruct. Custom resource management is a code smell unless you really need it.
Pass by const T& for reads, T + move for sinks. Avoid const T&& in signatures — it doesn’t mean what beginners think and prevents elision.
Turn on warnings + sanitizers in CI. -Wall -Wextra -Wpedantic -Werror plus -fsanitize=address,undefined catches the majority of UB before it ships. Sanitizer overhead is 2–3×, fine for tests.

Common trapsWatch out for

Iterator invalidation is silent. v.push_back(x) can reallocate; any iterator / pointer / reference to the vector is now dangling. Check container docs for which mutations invalidate which kinds of iterators.
Lambda capture by reference outlives the scope. A capture-by-reference lambda passed to std::async or stored in a member can outlive what it borrowed. Capture by value, or use shared_ptr when the lambda must own.
Use-after-move is "valid but unspecified". The compiler won’t stop you, but using a moved-from object beyond destruction or reassignment is undefined for some types and surprising for others. Treat moved-from as gone.

Go deeperSee also

C++ FAQ

Is C++ still worth learning in 2026?

Yes. C++ remains the language of choice for performance-critical systems, game engines, embedded firmware, and financial trading. C++20 and C++23 added ranges, concepts, coroutines, and modules that make modern C++ far safer and more expressive than legacy C++11 code.

What are smart pointers in C++?

Smart pointers — unique_ptr, shared_ptr, and weak_ptr — manage heap memory automatically via RAII so you never call delete manually. Use unique_ptr for sole ownership, shared_ptr when multiple owners share a resource, and weak_ptr to break reference cycles.

What is move semantics in C++?

Move semantics let a function transfer ownership of a resource from one object to another without copying. Marked with && (rvalue reference), moves are triggered by std::move() and enable containers like std::vector to resize without copying each element.

What is RAII in C++?

RAII (Resource Acquisition Is Initialization) ties a resource's lifetime to an object's scope. The resource is acquired in the constructor and released in the destructor, guaranteeing cleanup even if an exception is thrown. Smart pointers, file streams, and lock guards all follow RAII.

What is the difference between C++17, C++20, and C++23?

C++17 added structured bindings, if constexpr, and std::optional. C++20 was the biggest overhaul in a decade, introducing modules, coroutines, concepts, ranges, and std::format. C++23 refines ranges, adds std::print, std::expected, and std::flat_map.

What are C++ modules and should I use them?

C++ modules (C++20) replace header files with compiled binary interface units, eliminating textual inclusion and dramatically cutting build times for large codebases. Declare a module with export module mylib; and import it with import mylib;. Toolchain support is mature in MSVC and Clang 16+, and improving in GCC. For new large projects targeting C++20, modules are worth adopting; for existing codebases, the migration cost is high.