From "what does std::cout mean" to "I write lock-free telemetry engines for spacecraft." Modern C++ explained like you're human.
//gives you streams for input and output #include <iostream> #include <string> int main() { // std::cout is console out. << is the stream insertion operator. // Prefer '\n' over std::endl because endl forces a flush every time (slow!) std::cout << "Hello, Modern C++!\n"; std::string name; std::cout << "Enter your callsign: "; std::cin >> name; // reads single word std::cout << "Welcome aboard, " << name << "!\n"; return 0; }
g++ -std=c++20 main.cpp -o main. Always compile with at least C++17 or C++20 enabled!// auto deduces the exact type at COMPILE TIME (zero runtime cost) auto speed = 299792458; // deduced as int auto pi = 3.1415926535; // deduced as double auto flag = true; // deduced as bool auto msg = "Don't Panic"; // deduced as const char* // auto drops references & const by default! Use auto& or const auto& if you want them int val = 100; auto& ref = val; // ref is int& (modifying ref modifies val) const auto& cref = val; // cref is const int& (read-only reference) // decltype(expr) inspects the declared type of an expression decltype(val) anotherVal = 50; // anotherVal is int
auto makes complex iterator types readable. Instead of writing std::vector<std::pair<std::string, int>>::const_iterator, just write auto it.int x = 42; // 1. A Reference is an alias (another name for the same variable). // MUST be initialized, CANNOT be null, CANNOT be reseated to another var. int& ref = x; ref = 99; // x is now 99! // 2. Pass-by-Reference in functions prevents slow deep copies void boostEnergy(int& n) { n += 50; // modifies caller's variable directly } // 3. Const Reference: Fast (no copy) AND safe (cannot mutate) void printBigData(const std::string& text) { std::cout << text << "\n"; // text += "!"; // ERROR: cannot modify const reference }
int, float, bool) by value. Pass objects/strings/structs by const T&.#include <string> #include <iostream> std::string s1 = "Apollo"; std::string s2 = "11"; // Concatenation with + just works! std::string mission = s1 + " " + s2; // "Apollo 11" // Useful methods auto len = mission.length(); // or .size() -> 9 bool empty = mission.empty(); // false std::string sub = mission.substr(0, 6);// "Apollo" (start, length) mission.append(" - Moon Landing"); // Need C-style char* for an old C library? Use .c_str() const char* cStr = mission.c_str();
std::string manages its own heap memory automatically and grows dynamically. No manual malloc or strcpy bugs.#include <vector> #include <iostream> std::vector<int> coords = {10, 20, 30, 40, 50}; // Read-only (no copy, high performance) for (const auto& c : coords) { std::cout << c << " "; } // Mutate in place (by non-const reference) for (auto& c : coords) { c *= 2; // doubles each element in vector } // C++20 Structured Binding in range loop! struct Point { int x, y; }; std::vector<Point> pts = {{1,2}, {3,4}}; for (const auto& [px, py] : pts) { std::cout << "X:" << px << " Y:" << py << "\n"; }
// 1. constexpr evaluated entirely at COMPILE TIME! Zero CPU work at runtime. constexpr int square(int n) { return n * n; } constexpr int TABLE_SIZE = square(16); // computed by compiler as 256! // 2. consteval (C++20) FORCES compile-time evaluation (errors if called at runtime) consteval int mustBeConst(int v) { return v * 10; } // 3. 'using' is modern C++ typedef (cleaner, works with templates) using TelemetryID = uint64_t; using StringList = std::vector<std::string>;
constexpr over preprocessor macros like #define TABLE_SIZE 256. constexpr is type-safe and scoped!// In C++, class and struct are identical EXCEPT FOR DEFAULT VISIBILITY: // - 'struct' members default to PUBLIC // - 'class' members default to PRIVATE struct Vector2D { float x{0.0f}; // public by default! (in-class initializer) float y{0.0f}; }; class BankAccount { private: double balance{0.0}; // hidden from outside public: void deposit(double amount) { if (amount > 0) balance += amount; } double getBalance() const { return balance; } };
struct for plain passive data bags (POD). Use class when enforcing invariants and encapsulation.class Spaceship { private: std::string callsign; int crewCount; const int launchYear; // const members MUST use initializer lists! public: // 1. Parameterized Constructor with Initializer List (ALWAYS prefer this) Spaceship(std::string name, int crew, int year) : callsign(std::move(name)), crewCount(crew), launchYear(year) { // Body runs AFTER members are already constructed! } // 2. Default Constructor explicitly defaulted Spaceship() = default; // 3. Prevent implicit unwanted conversions using explicit explicit Spaceship(int crew) : callsign("Drone"), crewCount(crew), launchYear(2026) {} };
explicit so the compiler doesn't accidentally cast an integer into your entire class!class TelemetrySensor { private: double reading{0.0}; mutable int accessCount{0}; // mutable CAN be modified in const methods public: // Method marked const PROMISES not to mutate member variables double getReading() const { accessCount++; // legal because accessCount is mutable return reading; } // Method chaining using *this TelemetrySensor& setReading(double r) { this->reading = r; return *this; // return reference to current instance } }; // Usage: sensor.setReading(98.6).setReading(101.2);
class Vehicle { protected: float speed{0.0f}; // derived classes can access this, public cannot public: void accelerate(float amount) { speed += amount; } }; // 'public Vehicle' means Vehicle's public members stay public in Rocket class Rocket : public Vehicle { private: float fuelLevel{100.0f}; public: void igniteStage1() { speed += 1500.0f; // access protected base member fuelLevel -= 40.0f; } };
public inheritance. private or protected inheritance is rare and changes all base methods to hidden.class Astronaut { public: // ALWAYS make base class destructors virtual! (prevents memory leaks) virtual ~Astronaut() = default; // virtual enables runtime dynamic dispatch (vtables) virtual void performDuty() { std::cout << "Monitoring life support systems.\n"; } }; class Commander : public Astronaut { public: // 'override' makes the compiler verify this matches a base virtual function! void performDuty() override { std::cout << "Authorizing orbital insertion burn.\n"; } }; // Calling through base pointer invokes Commander::performDuty at runtime! Astronaut* astro = new Commander(); astro->performDuty(); // outputs Commander message! delete astro;
// Pure virtual method '= 0' makes class Abstract (cannot be instantiated) class ILogger { public: virtual ~ILogger() = default; virtual void log(const std::string& msg) = 0; // pure virtual }; class ConsoleLogger : public ILogger { public: void log(const std::string& msg) override { std::cout << "[LOG] " << msg << "\n"; } }; // 'final' on class prevents further inheritance class LockedLogger final : public ConsoleLogger {};
interface keyword like Java/C#. An abstract class with only pure virtual functions and a virtual destructor IS an interface.// RAII Rule: Bind the lifetime of a resource (heap memory, file, socket, mutex) // to the lifetime of a stack object! Destructor frees it AUTOMATICALLY when it leaves scope. class FileHandler { private: FILE* file; public: FileHandler(const char* filename, const char* mode) { file = fopen(filename, mode); } ~FileHandler() { if (file) fclose(file); // guaranteed to run even on exceptions! } }; void work() { FileHandler fh("telemetry.log", "w"); // Do stuff... if error or return happens, fclose is still called! }
#include <memory> struct SensorData { int pressure; }; void processData() { // 1. Always create with std::make_unique (exception-safe & fast) auto sensor = std::make_unique<SensorData>(); sensor->pressure = 1013; // 2. unique_ptr CANNOT BE COPIED! (exclusive single owner) // auto copy = sensor; // COMPILER ERROR! // 3. Ownership CAN be transferred with std::move std::unique_ptr<SensorData> newOwner = std::move(sensor); // sensor is now nullptr; newOwner owns the memory } // memory is automatically deleted here. Zero runtime overhead vs raw pointer!
std::unique_ptr should be your default choice 95% of the time for dynamically allocated heap objects.#include <memory> struct Node { int value; std::shared_ptr<Node> next; // weak_ptr does NOT increment ref count (breaks circular leak cycles!) std::weak_ptr<Node> prev; }; void example() { // std::make_shared allocates object + control block in 1 single memory chunk auto p1 = std::make_shared<Node>(); // use_count = 1 { auto p2 = p1; // copy increases use_count to 2 std::cout << p1.use_count(); // 2 } // p2 goes out of scope -> use_count drops to 1 // To use weak_ptr, must lock() it to get a temporary shared_ptr std::weak_ptr<Node> w = p1; if (auto locked = w.lock()) { locked->value = 42; } } // p1 leaves scope -> count hits 0 -> memory safely freed
#include <vector> #include <string> // Lvalue: Has a name/address (e.g. variable x). // Rvalue: Temporary value with no persistent name (e.g. "hi" + " world", 42). // T&& is an Rvalue Reference: binds to temporary objects ready to be stolen! class Buffer { int* data; size_t size; public: // Move Constructor: Steals pointer instead of copying huge array! Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; // zero out other so its destructor won't free it other.size = 0; } }; std::vector<std::string> v; std::string huge = "Gigabytes of telemetry data..."; v.push_back(std::move(huge)); // std::move casts huge to rvalue -> fast move!
/* RULE OF ZERO: If your class uses standard types (string, vector, unique_ptr), write ZERO special member functions. Compiler generates safe defaults! RULE OF THREE (C++98): If you write any of: 1. Destructor 2. Copy Constructor 3. Copy Assignment Operator ...you almost certainly must write all 3. RULE OF FIVE (Modern C++11+): When managing raw resources, write all 5: */ class ResourceHolder { public: ~ResourceHolder(); // 1. Destructor ResourceHolder(const ResourceHolder&); // 2. Copy Ctor ResourceHolder& operator=(const ResourceHolder&); // 3. Copy Assign ResourceHolder(ResourceHolder&&) noexcept; // 4. Move Ctor ResourceHolder& operator=(ResourceHolder&&) noexcept; // 5. Move Assign };
struct Vec2 { float x, y; // Overload + Vec2 operator+(const Vec2& rhs) const { return {x + rhs.x, y + rhs.y}; } // C++20 Three-way comparison operator (Spaceship operator <=>) // Generates <, <=, ==, !=, >=, > AUTOMATICALLY! auto operator<=>(const Vec2&) const = default; }; // Stream output operator overload (std::cout << vec) std::ostream& operator<<(std::ostream& os, const Vec2& v) { return os << "(" << v.x << ", " << v.y << ")"; }
#include <vector> #include <iostream> std::vector<int> nums = {1, 2, 3}; // reserve() pre-allocates memory capacity to avoid re-allocations nums.reserve(1000); nums.push_back(4); // appends element nums.emplace_back(5); // constructs element in-place (faster for objects) nums[0] = 10; // unchecked indexing (blazing fast) nums.at(1); // bounds-checked indexing (throws std::out_of_range) auto first = nums.front(); // 10 auto last = nums.back(); // 5 nums.pop_back(); // removes last element nums.clear(); // size becomes 0 (capacity preserved)
.reserve(N) on vectors if you know roughly how many items you will add! It prevents repeated heap reallocations.#include <unordered_map> #include <map> #include <string> // 1. std::unordered_map = Hash Table (O(1) average lookup, unordered) std::unordered_map<std::string, int> thrust; thrust["Merlin"] = 845; thrust["Raptor"] = 2200; // Check if key exists (C++20 .contains()) if (thrust.contains("Raptor")) { std::cout << thrust["Raptor"] << " kN\n"; } // 2. std::map = Red-Black Tree (O(log N) lookup, ALWAYS sorted by key) std::map<int, std::string> countdown; countdown[3] = "Ignition"; countdown[1] = "Liftoff";
// Syntax: [captures](parameters) -> return_type { body } int threshold = 50; // Capture by value [=] or [threshold] auto isHigh = [threshold](int val) -> bool { return val > threshold; }; // Capture by reference [&] (can modify outer variables) int counter = 0; auto tick = [&counter]() { counter++; }; // Generic Lambda with auto parameters (C++14+) auto printPair = [](const auto& a, const auto& b) { std::cout << a << ": " << b << "\n"; };
#include <algorithm> #include <ranges> #include <vector> std::vector<int> v = {5, 2, 8, 1, 9, 4}; // Modern C++20 std::ranges::sort (no need for v.begin(), v.end()!) std::ranges::sort(v); // Binary search / finding bool found = std::ranges::binary_search(v, 8); // Range Views pipeline (lazy evaluation! Zero allocations!) auto evenSquares = v | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; }); for (int n : evenSquares) { std::cout << n << " "; // 4 16 64 }
#include <optional> #include <variant> #include <string> // 1. std::optional: Replaces nullable pointers and magic error numbers (-1) std::optional<int> findSensor(int id) { if (id == 42) return 100; return std::nullopt; // empty result } auto res = findSensor(42); int val = res.value_or(0); // 100 if present, else fallback to 0 // 2. std::variant: Type-safe, memory-efficient tagged union std::variant<int, double, std::string> packet; packet = "Telemetry OK"; if (std::holds_alternative<std::string>(packet)) { std::cout << std::get<std::string>(packet) << "\n"; }
#include <string_view> #include <span> #include <iostream> // string_view is just a pointer + length. ZERO memory allocation! void parseHeader(std::string_view sv) { if (sv.starts_with("NASA")) { // C++20 std::cout << "Valid NASA packet: " << sv.substr(5) << "\n"; } } // std::span (C++20) represents any contiguous array view without copying void printBytes(std::span<const uint8_t> buffer) { for (auto b : buffer) std::cout << static_cast<int>(b) << " "; }
std::string_view for read-only function arguments instead of const std::string& whenever you might pass string literals or sub-slices!// 1. Function Template: Compiler generates concrete functions for each type used! template <typename T> T clampVal(T val, T minVal, T maxVal) { if (val < minVal) return minVal; if (val > maxVal) return maxVal; return val; } // 2. Class Template with Non-Type Template Parameter (e.g. fixed buffer size N) template <typename T, size_t N> class RingBuffer { private: T buffer[N]; size_t head{0}; public: void push(const T& item) { buffer[head++ % N] = item; } }; RingBuffer<float, 128> gyroBuffer;
#include <iostream> // 'Args... args' is a parameter pack containing ANY number of arguments template <typename... Args> void printAll(const Args&... args) { // C++17 Fold Expression: expands ((std::cout << arg1), (std::cout << arg2)...) ((std::cout << args << " "), ...); std::cout << "\n"; } // Summing multiple arguments at compile-time template <typename... Numbers> auto sumAll(Numbers... nums) { return (... + nums); // fold over binary operator + } printAll("Orbit", 42, 3.14, 'A'); // Orbit 42 3.14 A auto total = sumAll(10, 20, 30, 40); // 100
#include <concepts> #include <iostream> // Define custom concept: must support + and be copyable template <typename T> concept Numeric = std::integral<T> || std::floating_point<T>; // Use concept directly in template parameter template <Numeric T> T calcAcceleration(T force, T mass) { return force / mass; } // Or with the ultra-concise 'auto' concept syntax void printNumber(Numeric auto n) { std::cout << "Value: " << n << "\n"; } // Passing std::string will trigger a clear, readable compiler error!
#include <type_traits> #include <iostream> template <typename T> void serialize(const T& value) { // Discarded branch is NOT compiled for types that don't match! if constexpr (std::is_pointer_v<T>) { if (value) std::cout << *value << "\n"; } else if constexpr (std::is_integral_v<T>) { std::cout << "Int: " << value << "\n"; } else { std::cout << "Generic: " << value << "\n"; } }
| Rule | Rationale |
|---|---|
| No Dynamic Memory after Init | Allocate all heap during boot. Zero malloc/new during mission execution to prevent heap fragmentation crash. |
| Fixed Upper Loop Bounds | Every while/for loop MUST have a statically provable upper trip limit to guarantee termination. |
| No Unchecked Exceptions | Exceptions introduce unpredictable control flow and non-deterministic latency. Use std::expected or return codes. |
| Zero Undefined Behavior | Compile with -Wall -Wextra -Wpedantic -Wconversion -Werror and sanitize with UBSan / ASan. |
| No Multiple Inheritance with State | Avoid diamond inheritance complexity. Prefer composition or interfaces with pure virtual methods only. |
#include <thread> #include <mutex> #include <iostream> std::mutex telemetryMutex; int globalPacketCount = 0; void worker(std::stop_token st) { while (!st.stop_requested()) { { // RAII lock: locks on construction, unlocks automatically on exit! std::scoped_lock lock(telemetryMutex); globalPacketCount++; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } // C++20 std::jthread automatically requests stop and joins on destruction! void launch() { std::jthread t1(worker); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // t1 automatically stops & joins cleanly right here!
#include <atomic> // std::atomic executes hardware-level atomic CPU instructions (LOCK XADD etc.) // Zero OS kernel context-switches, zero mutex overhead! std::atomic<uint64_t> txPackets{0}; void onPacketReceived() { // Atomic fetch-and-add txPackets.fetch_add(1, std::memory_order_relaxed); } // Compare-And-Swap (CAS) loop: backbone of lock-free data structures std::atomic<int> head{0}; void updateMax(int newVal) { int curr = head.load(); while (newVal > curr && !head.compare_exchange_weak(curr, newVal)) { // loops until successfully swapped without data race } }
#include <new> // Modern CPUs load memory in 64-byte Cache Lines. // False Sharing: Two threads updating different variables on the SAME cache line // will destroy multi-core performance! struct alignas(64) ThreadCounter { std::atomic<uint64_t> counter{0}; uint8_t pad[56]; // pad out the remaining 64 bytes }; // Guaranteed each counter lives on its own dedicated cache line! ThreadCounter coreCounters[16];
alignas(64) in high-frequency trading or game engines can yield a 10x throughput boost.| Standard | Game-Changing Features |
|---|---|
| C++11 | auto, smart pointers, lambdas, move semantics (rvalues), range-for, nullptr, constexpr, threads. |
| C++14 | Generic lambdas, std::make_unique, relaxed constexpr, binary literals. |
| C++17 | std::string_view, std::optional, std::variant, structured bindings, if constexpr, fold expressions. |
| C++20 | Concepts, Ranges, std::jthread, Coroutines, Modules, consteval, std::format, Spaceship <=>. |
| C++23 | std::print / std::println, std::expected, deducing this, multidimensional subscript [x, y]. |
| Header | What it gives you |
|---|---|
| <iostream> | std::cout, std::cin, std::cerr |
| <vector> | std::vector dynamic array |
| <memory> | std::unique_ptr, std::shared_ptr, std::make_unique |
| <algorithm> | std::sort, std::find, std::ranges |
| <string_view> | std::string_view zero-copy strings |
| <optional> | std::optional nullable value container |
| <thread> | std::jthread, std::this_thread |
| <mutex> | std::mutex, std::scoped_lock |
| <atomic> | std::atomic lock-free primitives |
| <concepts> | std::integral, std::floating_point (C++20) |