A fast-track guide for developers coming from any language. Covers syntax, memory management, OOP, templates, the STL, and modern C++17/20 features — with a complete beginner project.
C++ is a compiled, statically typed, multi-paradigm language that compiles directly to native machine code — no runtime interpreter, no virtual machine. It gives you both high-level abstractions (classes, templates, STL) and low-level hardware control (pointers, manual memory). If you come from Python, JavaScript, or Java, the biggest mindset shifts are:
Source (.cpp) → compiler → binary (.exe / .out). No runtime needed. Errors caught at compile time.
Every variable needs a declared type. Types checked at compile time — no surprises at runtime.
You allocate (new) and free (delete) heap memory yourself. Smart pointers (C++11) automate this safely.
Declarations in .h (header). Definitions in .cpp (source). #include pulls headers into translation units.
# Compile a single file g++ -std=c++17 -Wall -Wextra -o myapp main.cpp # Compile multiple files g++ -std=c++17 -o myapp main.cpp utils.cpp classes.cpp # With optimisation (for release builds) g++ -std=c++17 -O2 -o myapp main.cpp # Run ./myapp # Linux / macOS myapp.exe # Windows
-std=c++17 sets the language version. -Wall -Wextra enable all warnings — always use these while learning. -O2 enables optimisation for production. -g adds debug symbols for use with gdb.
Every C++ program starts at main(). The structure is: preprocessor directives, then declarations/definitions, then main(), then any function definitions. Statements end with ;. Blocks are delimited by { }.
// ── 1. Preprocessor directives ───────────────────────────────── // Processed before compilation. Not C++ statements — no semicolon. #include <iostream> // standard I/O (std::cout, std::cin) #include <string> // std::string #include <vector> // std::vector #define MAX_SIZE 100 // compile-time text substitution // ── 2. Using declarations (optional shortcut) ────────────────── using namespace std; // lets you write cout instead of std::cout // Avoid in header files — pollutes namespace // ── 3. Global constants (prefer over #define) ───────────────── const int VERSION = 1; constexpr double PI = 3.14159265358979; // C++11: compile-time const // ── 4. Function declaration (prototype) ─────────────────────── void greet(const string& name); // declare before use // ── 5. main() — program entry point ─────────────────────────── // Returns int: 0 = success, non-zero = error code int main() { greet("World"); return 0; // required — tells OS the program succeeded } // OR with command-line arguments: int main(int argc, char* argv[]) { // argc = argument count (includes program name) // argv = array of argument strings for (int i = 0; i < argc; ++i) cout << argv[i] << "\n"; return 0; } // ── 6. Function definition ───────────────────────────────────── void greet(const string& name) { cout << "Hello, " << name << "!\n"; }
#pragma once // include guard — prevents double inclusion #include <string> // Declare, don't define (usually) int add(int a, int b); void printLine(const std::string& s); // Inline functions CAN be defined in headers inline int square(int x) { return x * x; }
#include "utils.h" #include <iostream> int add(int a, int b) { return a + b; } void printLine(const std::string& s) { std::cout << s << '\n'; }
| Type | Size (typical) | Range / Notes |
|---|---|---|
| bool | 1 byte | true / false |
| char | 1 byte | ASCII character (-128–127 or 0–255) |
| short | 2 bytes | -32,768 – 32,767 |
| int | 4 bytes | -2.1B – 2.1B · most common integer |
| long | 4–8 bytes | Platform-dependent |
| long long | 8 bytes | ±9.2 × 10¹⁸ |
| float | 4 bytes | ~7 significant digits |
| double | 8 bytes | ~15 significant digits · prefer this |
| long double | 8–16 bytes | Extended precision |
| unsigned int | 4 bytes | 0 – 4.29B · add unsigned to any integer |
| std::string | object | #include <string> · preferred string type |
| auto | inferred | C++11: compiler deduces the type |
// ── Declaration and initialization ───────────────────────────── int count = 0; double pi = 3.14159; bool isReady = true; char grade = 'A'; // single quotes for char std::string name = "Alice"; // double quotes for string // ── Uniform initialization (C++11 — prefer this style) ───────── int x{42}; // braces: will NOT compile if narrowing double y{3.14}; std::string s{"hello"}; // ── auto — compiler deduces type ──────────────────────────────── auto a = 42; // int auto b = 3.14; // double auto c = "hello"; // const char* — use std::string{"hello"} instead! auto d = std::string{"hi"}; // std::string // ── const and constexpr ───────────────────────────────────────── const int MAX = 100; // runtime const — cannot change constexpr double E = 2.71828; // C++11: must be known at compile time // ── Numeric literals ──────────────────────────────────────────── int hex = 0xFF; // hexadecimal = 255 int oct = 0777; // octal = 511 int bin = 0b10110000; // C++14 binary literal long l = 100L; // L suffix = long double d2 = 1.5e3; // scientific notation = 1500.0 int million = 1'000'000; // C++14 digit separator // ── Type casting ──────────────────────────────────────────────── int i = 7; double d3 = static_cast<double>(i); // C++ cast — always prefer this double d4 = (double)i; // C-style cast — works but less safe // ── Fixed-width types from <cstdint> ─────────────────────────── #include <cstdint> int8_t a8 = 127; // exactly 8 bits signed uint32_t u32 = 4294967295U; // exactly 32 bits unsigned int64_t i64 = -1LL; // exactly 64 bits signed // ── Storage class modifiers ───────────────────────────────────── static int counter = 0; // persists between function calls extern int globalVar; // declares var defined in another .cpp file
| Category | Operators | Example | Notes |
|---|---|---|---|
| Arithmetic | + - * / % | a % b | Integer / truncates toward zero. % = remainder. |
| Increment | ++ -- | ++i; i++; | Pre (++i) increments first. Post (i++) returns old value. |
| Compound | += -= *= /= %= | x += 5; | Shorthand for x = x + 5 |
| Comparison | == != < > <= >= | x == 10 | Returns bool |
| Logical | && || ! | a && !b | Short-circuit evaluation |
| Bitwise | & | ^ ~ << >> | x &= 0x0F; | Essential for flags, masks, hardware registers |
| Ternary | ? : | y = x>0 ? x : -x; | Inline if/else expression |
| Comma | , | for(i=0,j=10; ...) | Evaluate left, discard, return right |
| sizeof | sizeof(type) | sizeof(int) | Bytes occupied by a type or variable |
| Scope | :: | std::cout | Namespace / class member access |
| Member | . -> | obj.fn(); ptr->fn(); | . for objects, -> for pointers |
| Addressof | & | &x | Address of a variable |
| Dereference | * | *ptr | Value at pointer address |
// ── Pre vs post increment ─────────────────────────────────────── int i = 5; int a = ++i; // i becomes 6, a = 6 (pre) int b = i++; // b = 6 (old value), then i becomes 7 (post) // ── Integer division vs float division ───────────────────────── int divI = 7 / 2; // 3 (truncates) double divD = 7.0 / 2.0; // 3.5 double divM = static_cast<double>(7) / 2; // 3.5 — cast one operand // ── Bitwise for flags (common in systems programming) ────────── constexpr uint8_t FLAG_READ = 0b00000001; constexpr uint8_t FLAG_WRITE = 0b00000010; constexpr uint8_t FLAG_EXEC = 0b00000100; uint8_t perms = FLAG_READ | FLAG_WRITE; // set bits: 0b00000011 perms &= ~FLAG_WRITE; // clear write bit: 0b00000001 perms ^= FLAG_EXEC; // toggle exec bit: 0b00000101 bool canRead = perms & FLAG_READ; // test bit: true // ── Operator precedence reminder ─────────────────────────────── // * / % then + - then << >> then < > then == != // then & then ^ then | then && then || then ?: // When in doubt — use parentheses! bool result = (3 + 4) * (2 < 5); // 7 * 1 = 7 (truthy)
// ── IF / ELSE IF / ELSE ───────────────────────────────────────── int score = 85; if (score >= 90) { std::cout << "A\n"; } else if (score >= 80) { std::cout << "B\n"; } else { std::cout << "C or below\n"; } // ── if with initializer (C++17) ────────────────────────────────── if (auto val = getValue(); val > 0) { // val scoped to if block std::cout << val << "\n"; } // ── SWITCH ─────────────────────────────────────────────────────── char grade = 'B'; switch (grade) { case 'A': std::cout << "Excellent\n"; break; case 'B': std::cout << "Good\n"; break; case 'C': case 'D': std::cout << "Passing\n"; break; // fall-through default: std::cout << "Failing\n"; } // ── FOR LOOP ───────────────────────────────────────────────────── for (int i = 0; i < 10; ++i) { // classic C-style for std::cout << i << " "; } // ── RANGE-BASED FOR (C++11) ────────────────────────────────────── std::vector<int> nums = {1, 2, 3, 4, 5}; for (int n : nums) std::cout << n << " "; // copy for (const int& n : nums) std::cout << n << " "; // const ref (no copy) for (auto& n : nums) n *= 2; // mutate via ref for (auto&& n : nums) ; // universal ref (C++17) // ── WHILE LOOP ─────────────────────────────────────────────────── int n = 1; while (n < 1024) { n *= 2; } // ── DO-WHILE — executes at least once ──────────────────────────── int choice; do { std::cout << "Enter 1-5: "; std::cin >> choice; } while (choice < 1 || choice > 5); // ── BREAK / CONTINUE / GOTO ───────────────────────────────────── for (int i = 0; i < 20; ++i) { if (i == 10) break; // exit loop if (i % 2 == 0) continue; // skip even numbers std::cout << i << " "; } // goto exists but avoid it — breaks structured flow
// ── Syntax: return_type name(params) { body } ────────────────── int add(int a, int b) { return a + b; } // ── Default parameters (must be rightmost) ────────────────────── void print(std::string msg, int width = 80, char fill = ' ') { std::cout << std::string(width, fill) << "\n" << msg << "\n"; } print("Hello"); // uses both defaults print("Hello", 40); // width=40, fill=' ' print("Hello", 40, '-'); // all explicit // ── Pass by value, reference, const reference ──────────────────── void byValue (int x) { x = 99; } // copy — original unchanged void byRef (int& x) { x = 99; } // modifies original void byConstRef(const std::string& s) { // no copy, no modify std::cout << s; } // ── Function overloading ───────────────────────────────────────── double area(double r) { return 3.14159 * r * r; } // circle double area(double w, double h) { return w * h; } // rectangle // Compiler picks the right version by argument count/type // ── Returning multiple values ──────────────────────────────────── #include <tuple> std::tuple<int, double, std::string> getStats() { return {42, 3.14, "hello"}; } auto [count, val, label] = getStats(); // C++17 structured binding // ── Recursion ──────────────────────────────────────────────────── long long factorial(int n) { if (n <= 1) return 1; // base case return n * factorial(n - 1); // recursive case } // ── Lambda expressions (C++11) ─────────────────────────────────── // [capture](params) -> return_type { body } auto square = [](int x) { return x * x; }; auto add2 = [](int a, int b) -> int { return a + b; }; int factor = 3; auto multiply = [factor](int x) { return x * factor; }; // capture by value auto reset = [&factor]() { factor = 0; }; // capture by reference auto captureAll = [=](int x) { return x + factor; }; // capture all by val // ── Function pointers ──────────────────────────────────────────── int (*funcPtr)(int, int) = add; // pointer to function funcPtr(3, 4); // call through pointer // ── std::function (C++11) — type-erased callable ───────────────── #include <functional> std::function<int(int,int)> fn = add; // can hold any callable fn = [](int a, int b) { return a - b; }; // reassign to lambda // ── Inline functions ───────────────────────────────────────────── inline int clamp(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); }
// ── C-style arrays (fixed size, stack) ───────────────────────── int primes[5] = {2, 3, 5, 7, 11}; // size MUST be known at compile time int zeros[10] = {}; // zero-initialize all elements int len = sizeof(primes) / sizeof(primes[0]); // = 5 // ── std::array (C++11) — fixed size, bounds-aware ─────────────── #include <array> std::array<int, 5> arr = {2, 3, 5, 7, 11}; arr.size(); // 5 arr.at(2); // 5 — bounds-checked (throws on bad index) arr[2]; // 5 — no bounds check (faster) arr.front(); // first arr.back(); // last // ── 2D arrays ──────────────────────────────────────────────────── int matrix[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; int val = matrix[1][2]; // 6 (row 1, col 2) // ── C-style strings (char arrays) ──────────────────────────────── #include <cstring> char greeting[] = "Hello"; // 6 bytes: 5 chars + '\0' null terminator char buf[64]; strcpy(buf, greeting); // copy — dangerous if buf too small! strcat(buf, " World"); // concatenate strlen(greeting); // 5 (excludes null) strcmp("abc", "abc"); // 0 if equal // ── std::string (PREFER THIS) ──────────────────────────────────── #include <string> std::string s1 = "Hello"; std::string s2{" World"}; std::string s3 = s1 + s2; // "Hello World" — operator+ concatenates s3 += "!"; // "Hello World!" s3.length(); // 12 s3.size(); // same as length() s3.empty(); // false s3[0]; // 'H' s3.at(0); // 'H' (bounds-checked) s3.substr(6, 5); // "World" (pos, len) s3.find("World"); // 6 (or string::npos if not found) s3.replace(6, 5, "C++"); // "Hello C++!" s3.erase(5); // remove from pos 5 onward s3.insert(5, ", dear"); // insert at position std::string(5, '*'); // "*****" // Convert to/from numeric types #include <string> std::string numStr = std::to_string(42); // int → string int i = std::stoi("42"); // string → int double d = std::stod("3.14"); // string → double // ── std::string_view (C++17) — non-owning reference ───────────── #include <string_view> void process(std::string_view sv) { // no copy! works on string, char* std::cout << sv.substr(0, 5) << "\n"; }
Pointers are one of the most important (and misunderstood) features of C++. A pointer holds the memory address of another variable. A reference is an alias for an existing variable — think of it as a non-nullable, always-bound pointer with cleaner syntax.
// ── POINTERS ───────────────────────────────────────────────────── int value = 42; int* ptr = &value; // ptr holds the ADDRESS of value // & = "address of" operator std::cout << ptr; // prints the address (e.g. 0x7ffd…) std::cout << *ptr; // prints 42 — * = "dereference" (value at address) *ptr = 100; // modifies value through the pointer std::cout << value; // 100 // ── Null pointers ──────────────────────────────────────────────── int* p = nullptr; // C++11: prefer nullptr over NULL or 0 if (p != nullptr) *p = 5; // always check before dereferencing! // ── Pointer arithmetic ─────────────────────────────────────────── int arr[] = {10, 20, 30}; int* p2 = arr; // array name is a pointer to first element std::cout << *(p2 + 1); // 20 — advances by sizeof(int) p2++; // now points to arr[1] // ── Pointer to const vs const pointer ─────────────────────────── const int* cp1 = &value; // pointer to const: can't change *cp1 int* const cp2 = &value; // const pointer: can't change cp2 (address) const int* const cp3 = &value; // both const // ── REFERENCES ─────────────────────────────────────────────────── int x = 5; int& ref = x; // ref IS x — same memory, different name ref = 10; // modifies x — no dereference needed std::cout << x; // 10 // Key differences from pointers: // - References can't be null (must bind at declaration) // - References can't be rebound to another variable // - No arithmetic on references // - Cleaner syntax for function params // ── Pass by pointer vs pass by reference ───────────────────────── void doubleByPtr(int* p) { *p *= 2; } // must dereference void doubleByRef(int& r) { r *= 2; } // no dereference needed int n = 5; doubleByPtr(&n); // must pass address with & doubleByRef(n); // no & needed — cleaner // Both result in n = 20 // ── void* — generic pointer ─────────────────────────────────────── void* vp = &value; // can point to anything int* ip = static_cast<int*>(vp); // must cast back to use
In C++, memory lives in two places: the stack (automatic, fast, limited size, destroyed at scope exit) and the heap (manual, large, you control lifetime). Modern C++ uses smart pointers to manage heap memory automatically.
#include <memory> // for smart pointers // ── RAW new / delete (C-style — avoid in modern C++) ──────────── int* raw = new int(42); // allocate single int on heap *raw = 100; delete raw; // MUST free — memory leak if forgotten! raw = nullptr; // good habit: null out after delete int* arr = new int[10]; // allocate array arr[0] = 1; delete[] arr; // MUST use delete[] for arrays! // ── std::unique_ptr — sole ownership, auto-deletes ────────────── std::unique_ptr<int> up = std::make_unique<int>(42); // C++14 *up = 100; // Automatically deleted when up goes out of scope — no delete needed! std::unique_ptr<int> up2 = std::move(up); // transfer ownership (move semantics) // up is now null — only up2 owns the resource // ── std::shared_ptr — shared ownership, ref-counted ───────────── std::shared_ptr<int> sp1 = std::make_shared<int>(42); std::shared_ptr<int> sp2 = sp1; // both own the int — ref count = 2 sp1.use_count(); // 2 sp1.reset(); // sp1 releases — ref count = 1 // Deleted when last shared_ptr goes out of scope (ref count = 0) // ── std::weak_ptr — non-owning observer ────────────────────────── std::weak_ptr<int> wp = sp2; // doesn't increment ref count if (auto locked = wp.lock()) { // check if still alive before use std::cout << *locked; } // ── Stack vs Heap summary ───────────────────────────────────────── // Stack: int x = 5; — fast, auto lifetime, size limited // Heap: auto p = make_unique() — manual lifetime, large, flexible // RULE: prefer stack. Use heap only for: // • Objects that must outlive their scope // • Very large data (stack is typically 1–8 MB) // • Polymorphic objects (virtual dispatch needs pointer/reference) // ── RAII — Resource Acquisition Is Initialization ───────────────── // The core C++ idiom: acquire resource in constructor, // release in destructor. Smart pointers, std::fstream, std::mutex // all follow RAII — cleanup happens automatically.
If you manage a raw resource (raw pointer, file handle) in a class, you must define or delete: destructor, copy constructor, copy assignment, move constructor, move assignment (Rule of Five). If you use RAII wrappers exclusively (smart pointers, std::string, std::vector), you can rely on compiler-generated defaults (Rule of Zero) — prefer this.
#include <string> #include <iostream> class BankAccount { private: // only accessible inside the class std::string _owner; double _balance; static int _totalAccounts; // shared by ALL instances public: // accessible from anywhere // ── Constructor — member initializer list (preferred) ──────── BankAccount(std::string owner, double initial = 0.0) : _owner{std::move(owner)}, _balance{initial} { ++_totalAccounts; if (_balance < 0) throw std::invalid_argument("Negative balance"); } // ── Destructor ─────────────────────────────────────────────── ~BankAccount() { --_totalAccounts; } // ── Getters (const methods — promise not to modify the object) const std::string& owner() const { return _owner; } double balance() const { return _balance; } // ── Member functions ───────────────────────────────────────── void deposit(double amount) { if (amount <= 0) throw std::invalid_argument("Must be positive"); _balance += amount; } bool withdraw(double amount) { if (amount > _balance) return false; _balance -= amount; return true; } // ── Operator overloading ───────────────────────────────────── bool operator<(const BankAccount& other) const { return _balance < other._balance; } // ── Stream output (friend: can access private members) ─────── friend std::ostream& operator<<(std::ostream& os, const BankAccount& a) { return os << a._owner << ": $" << a._balance; } // ── Static method — access class-level data ────────────────── static int count() { return _totalAccounts; } }; // Define the static member OUTSIDE the class int BankAccount::_totalAccounts = 0; // ── Usage ───────────────────────────────────────────────────────── BankAccount acc{"Alice", 1000.0}; // calls constructor acc.deposit(250.0); acc.withdraw(100.0); std::cout << acc << "\n"; // uses operator<< std::cout << BankAccount::count(); // static member: class::method() // ── Struct vs Class ─────────────────────────────────────────────── // struct: members are PUBLIC by default // class: members are PRIVATE by default // Otherwise identical — use struct for plain data, class for OOP struct Point { double x, y; double dist() const { return std::sqrt(x*x + y*y); } };
class Shape { protected: // accessible by this class AND derived classes std::string _color; public: Shape(std::string color) : _color{std::move(color)} {} // ── pure virtual — derived classes MUST override ───────────── virtual double area() const = 0; // = 0 makes Shape abstract virtual double perimeter() const = 0; // ── virtual with default — derived classes CAN override ────── virtual void describe() const { std::cout << _color << " shape, area=" << area() << "\n"; } // ── Virtual destructor — REQUIRED for polymorphic base classes virtual ~Shape() = default; }; class Circle : public Shape { // public inheritance private: double _radius; public: Circle(std::string c, double r) : Shape{std::move(c)}, _radius{r} {} double area() const override { return 3.14159 * _radius * _radius; } double perimeter() const override { return 2 * 3.14159 * _radius; } // 'override' keyword: compiler error if signature doesn't match }; class Rectangle : public Shape { private: double _w, _h; public: Rectangle(std::string c, double w, double h) : Shape{std::move(c)}, _w{w}, _h{h} {} double area() const override { return _w * _h; } double perimeter() const override { return 2 * (_w + _h); } }; // ── Polymorphism — same interface, different behaviour ─────────── #include <vector> #include <memory> std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>("red", 5.0)); shapes.push_back(std::make_unique<Rectangle>("blue", 4.0, 6.0)); for (const auto& s : shapes) { s->describe(); // virtual dispatch: calls correct override at runtime std::cout << s->area() << "\n"; } // ── dynamic_cast — safe downcast ───────────────────────────────── if (auto* circ = dynamic_cast<Circle*>(shapes[0].get())) { std::cout << "It's a circle!\n"; // safe — returns nullptr if wrong type }
Templates are C++'s generic programming mechanism. They generate code at compile time for any type you pass, with zero runtime overhead. This is what makes STL containers like std::vector<T> work for any type.
// ── Function template ──────────────────────────────────────────── template<typename T> T maxOf(T a, T b) { return (a > b) ? a : b; } // Compiler generates separate versions for each type used: maxOf(3, 7); // int version maxOf(3.14, 2.72); // double version maxOf(std::string{"a"}, std::string{"b"}); // string version // ── Multiple template parameters ──────────────────────────────── template<typename T, typename U> auto multiply(T a, U b) -> decltype(a * b) { return a * b; } // ── Class template ──────────────────────────────────────────────── template<typename T> class Stack { private: std::vector<T> _data; public: void push(const T& val) { _data.push_back(val); } void push(T&& val) { _data.push_back(std::move(val)); } void pop() { _data.pop_back(); } T& top() { return _data.back(); } bool empty() const { return _data.empty(); } size_t size() const { return _data.size(); } }; Stack<int> istack; // int stack Stack<std::string> sstack; // string stack istack.push(42); istack.push(17); std::cout << istack.top(); // 17 // ── Template specialization ────────────────────────────────────── template<> // specialization for bool class Stack<bool> { // custom implementation for bool std::vector<bool> _data; // std::vector<bool> is bit-packed public: void push(bool v) { _data.push_back(v); } }; // ── Concepts (C++20) — constrain template types ────────────────── #include <concepts> template<std::integral T> // T must satisfy the 'integral' concept T safeDivide(T a, T b) { if (b == 0) throw std::domain_error("div by zero"); return a / b; } template<typename T> concept Printable = requires(T t) { // custom concept { std::cout << t } -> std::same_as<std::ostream&>; };
| Container | Header | Access / Notes |
|---|---|---|
| vector<T> | <vector> | Dynamic array. O(1) random access. O(1) amortised push_back. |
| array<T,N> | <array> | Fixed-size array. Stack-allocated. O(1) random access. |
| deque<T> | <deque> | Double-ended queue. O(1) push/pop at both ends. |
| list<T> | <list> | Doubly linked list. O(1) insert/erase anywhere, no random access. |
| map<K,V> | <map> | Sorted key-value BST. O(log n) operations. |
| unordered_map<K,V> | <unordered_map> | Hash map. O(1) average operations. |
| set<T> | <set> | Sorted unique values. O(log n). |
| unordered_set<T> | <unordered_set> | Hash set. O(1) average lookup. |
| stack<T> | <stack> | LIFO adaptor. push/pop/top. |
| queue<T> | <queue> | FIFO adaptor. push/pop/front. |
| priority_queue<T> | <queue> | Max-heap. top() is always largest. |
| pair<A,B> | <utility> | Two values. .first, .second |
| tuple<T...> | <tuple> | N values of different types. get<N>(t) |
| optional<T> | <optional> | C++17. May or may not contain a value. Safe "no value" state. |
| variant<T...> | <variant> | C++17. Type-safe union. Holds one of several types. |
#include <vector> #include <map> #include <algorithm> #include <numeric> #include <optional> // ── vector ──────────────────────────────────────────────────────── std::vector<int> v = {5, 3, 8, 1, 9, 2}; v.push_back(7); v.pop_back(); v.insert(v.begin() + 2, 42); // insert at index 2 v.erase(v.begin() + 2); // remove at index 2 v.resize(10, 0); // grow to 10, fill new with 0 v.reserve(100); // pre-allocate — avoid reallocation v.clear(); // remove all elements v.size(); // element count v.empty(); // true if empty v.front(); v.back(); // first and last element // ── std::map (sorted) ───────────────────────────────────────────── std::map<std::string, int> scores; scores["Alice"] = 95; scores["Bob"] = 87; scores.count("Alice"); // 1 if key exists, 0 if not scores.find("Bob"); // returns iterator (or end()) scores.erase("Bob"); for (auto& [key, val] : scores) // C++17 structured binding std::cout << key << ": " << val << "\n"; // ── <algorithm> ────────────────────────────────────────────────── std::vector<int> nums = {5, 3, 8, 1, 9}; std::sort(nums.begin(), nums.end()); // ascending std::sort(nums.begin(), nums.end(), std::greater<>{}); // descending std::sort(nums.begin(), nums.end(), [](int a, int b){ return a < b; }); auto it = std::find(nums.begin(), nums.end(), 8); // find by value if (it != nums.end()) std::cout << "found at " << (it - nums.begin()); int sum = std::accumulate(nums.begin(), nums.end(), 0); // sum int mx = *std::max_element(nums.begin(), nums.end()); // max int mn = *std::min_element(nums.begin(), nums.end()); // min std::reverse(nums.begin(), nums.end()); // reverse std::for_each(nums.begin(), nums.end(), [](int& n){ n*=2; }); // transform in-place long cnt = std::count_if(nums.begin(), nums.end(), [](int n){ return n>5; }); std::transform(nums.begin(), nums.end(), nums.begin(), [](int n){ return n * n; }); // square each element // ── std::optional (C++17) ──────────────────────────────────────── std::optional<int> safeDivide(int a, int b) { if (b == 0) return std::nullopt; // no value return a / b; // wraps the value } auto result = safeDivide(10, 2); if (result.has_value()) std::cout << result.value() << "\n"; // 5 int r = result.value_or(0); // default if empty
#include <stdexcept> #include <exception> // ── try / catch / throw ────────────────────────────────────────── try { throw std::runtime_error("Something went wrong"); } catch (const std::invalid_argument& e) { std::cout << "Invalid: " << e.what(); } catch (const std::out_of_range& e) { std::cout << "Range: " << e.what(); } catch (const std::runtime_error& e) { std::cout << "Runtime: " << e.what(); } catch (const std::exception& e) { std::cout << "Exception: " << e.what(); } catch (...) { std::cout << "Unknown exception\n"; } // ── Standard exception hierarchy ───────────────────────────────── // std::exception // ├── std::logic_error // │ ├── invalid_argument — bad function argument // │ ├── domain_error — math domain error // │ ├── length_error — too long // │ └── out_of_range — index/value out of range // └── std::runtime_error // ├── range_error — result out of range // ├── overflow_error — arithmetic overflow // └── underflow_error — arithmetic underflow // ── Custom exceptions ───────────────────────────────────────────── class DatabaseError : public std::runtime_error { private: int _code; public: DatabaseError(const std::string& msg, int code) : std::runtime_error(msg), _code{code} {} int code() const { return _code; } }; try { throw DatabaseError("Connection failed", 503); } catch (const DatabaseError& e) { std::cout << e.what() << " (code " << e.code() << ")\n"; } // ── noexcept — promises function won't throw ───────────────────── double safeSqrt(double x) noexcept { // compiler can optimise return x >= 0 ? std::sqrt(x) : 0.0; }
#include <fstream> #include <sstream> #include <string> // ── Write to file ───────────────────────────────────────────────── std::ofstream outFile{"data.txt"}; // opens for writing (truncates) if (!outFile) throw std::runtime_error("Cannot open file"); outFile << "Line 1\n" << "Line 2\n"; outFile.close(); // closes automatically at scope exit (RAII) // Append mode std::ofstream appendFile{"data.txt", std::ios::app}; appendFile << "Appended line\n"; // ── Read entire file line by line ───────────────────────────────── std::ifstream inFile{"data.txt"}; std::string line; while (std::getline(inFile, line)) { std::cout << line << "\n"; } // ── Read all content at once ────────────────────────────────────── std::ifstream f{"data.txt"}; std::string content{(std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()}; // ── Read formatted data ─────────────────────────────────────────── std::ifstream data{"scores.txt"}; std::string name; int score; while (data >> name >> score) { // reads whitespace-delimited tokens std::cout << name << ": " << score << "\n"; } // ── String streams — process strings like streams ──────────────── std::ostringstream oss; oss << "Value: " << 42 << ", Pi: " << std::fixed << 3.14; std::string result = oss.str(); std::istringstream iss{"10 20 30"}; int a, b, c; iss >> a >> b >> c; // a=10, b=20, c=30 // ── I/O formatting ──────────────────────────────────────────────── #include <iomanip> std::cout << std::fixed << std::setprecision(2) << 3.14159; // 3.14 std::cout << std::setw(10) << std::left << "name"; // left-align in 10 std::cout << std::hex << 255; // ff std::cout << std::oct << 255; // 377 std::cout << std::boolalpha << true; // "true" instead of 1
| Feature | Version | Example | Purpose |
|---|---|---|---|
| auto | C++11 | auto x = 42; | Type deduction — less boilerplate |
| Range-based for | C++11 | for (auto& v : vec) | Iterate without index boilerplate |
| Lambda | C++11 | [x](int n){ return n+x; } | Inline anonymous functions |
| nullptr | C++11 | int* p = nullptr; | Type-safe null pointer |
| unique_ptr / shared_ptr | C++11 | make_unique<T>() | Automatic memory management |
| move semantics | C++11 | std::move(v) | Transfer ownership without copying |
| constexpr | C++11 | constexpr int N = 42; | Compile-time evaluation |
| static_assert | C++11 | static_assert(N>0); | Compile-time assertion |
| initializer_list | C++11 | fn({1,2,3}) | Brace-initialized argument lists |
| generic lambda | C++14 | [](auto x){ return x; } | Lambda with deduced param type |
| if init-statement | C++17 | if(auto v=f(); v>0) | Scoped init inside if |
| structured bindings | C++17 | auto [k,v] = pair; | Unpack pairs/tuples/structs |
| std::optional | C++17 | optional<int> v; | Value that may not exist |
| std::variant | C++17 | variant<int,str> | Type-safe union |
| Concepts | C++20 | template<std::integral T> | Constrain template type params |
| Ranges | C++20 | views::filter | views::transform | Composable lazy range algorithms |
| Coroutines | C++20 | co_yield, co_await | Suspendable functions / async |
| Modules | C++20 | import std; | Replacement for #include |
// ── Move semantics ─────────────────────────────────────────────── std::vector<int> makeData() { std::vector<int> data(1'000'000, 0); return data; // NRVO or move — NOT a million-element copy } auto v = makeData(); // move-constructed — fast! std::vector<int> src = {1, 2, 3}; std::vector<int> dst = std::move(src); // src is now empty — no copy // ── C++17 structured bindings ──────────────────────────────────── std::map<std::string, int> scores = {{"A",1}, {"B",2}}; for (auto& [key, val] : scores) std::cout << key << "=" << val << "\n"; auto [x, y, z] = std::tuple{1, 2.0, "three"}; // ── C++17 if with initializer ──────────────────────────────────── if (auto it = scores.find("A"); it != scores.end()) { std::cout << it->second; // it is scoped to the if block } // ── C++20 Ranges ───────────────────────────────────────────────── #include <ranges> std::vector<int> nums = {1,2,3,4,5,6}; auto evens = nums | std::views::filter ([](int n){ return n % 2 == 0; }) | std::views::transform([](int n){ return n * n; }); for (int v : evens) std::cout << v << " "; // 4 16 36 — lazy evaluated!
/* * Hello World — C++17 * Demonstrates the minimal structure of a C++ program: * preprocessor include, main(), stream output, return value. * * Compile: g++ -std=c++17 -o hello helloworld.cpp * Run: ./hello (Linux/macOS) * hello.exe (Windows) */ #include <iostream> // standard input/output streams #include <string> // std::string int main() { // std::cout = character output stream // << = stream insertion operator // std::endl = newline + flush (use "\n" for performance) std::cout << "Hello, World!" << "\n"; // Multiple insertions in one statement std::string name = "C++"; int year = 1979; std::cout << "Language: " << name << ", Born: " << year << "\n"; // Reading input std::string user; std::cout << "Enter your name: "; std::getline(std::cin, user); // reads whole line (including spaces) std::cout << "Hello, " << user << "!\n"; return 0; // 0 = success }
A complete command-line Inventory Management system that demonstrates virtually every core C++ concept in one cohesive, runnable program. No external dependencies — compile with a single command.
Classes · Inheritance · Virtual/override · Templates · STL (vector, map, algorithm) · Smart pointers · File I/O · Exceptions · Lambdas · Structured bindings · optional · Modern C++17
g++ -std=c++17 -Wall -o inventory inventory.cpp
./inventory
a (add), l (list), s (search), r (restock), p (report), e (export), q (quit)
inventory.cpp (single file) · Writes inventory.txt on export
/* * ═══════════════════════════════════════════════════════════════ * INVENTORY MANAGER — C++17 Comprehensive Beginner Project * Demonstrates nearly every core C++ concept in one program. * ═══════════════════════════════════════════════════════════════ * * CONCEPTS DEMONSTRATED: * Preprocessor / #include Namespaces * Primitive types / auto const / constexpr * Uniform initialization C-style and std::string * Arithmetic / logical ops Bitwise ops (flags) * if/else, switch for, while, do-while * Range-based for break / continue * Functions / overloading Default params / references * Recursion Lambdas (capture by value/ref) * std::function Classes (private/public/protected) * Member initializer lists Constructors / destructors * Operator overloading friend / static members * Inheritance / virtual override / final * Abstract base class Polymorphism * Templates (function+class) Template specialisation * std::vector / std::map std::algorithm (sort, find_if) * std::unique_ptr Move semantics (std::move) * try/catch/throw Custom exceptions * File I/O (ofstream) std::ostringstream * std::optional Structured bindings (C++17) * if with initializer (C++17) std::numeric accumulate * static_assert std::tuple / get<>() */ #include <iostream> #include <string> #include <vector> #include <map> #include <algorithm> #include <numeric> #include <memory> #include <functional> #include <optional> #include <fstream> #include <sstream> #include <iomanip> #include <stdexcept> #include <limits> #include <tuple> // ── NAMESPACE ──────────────────────────────────────────────────────────── namespace Inv { // ── CONSTANTS ──────────────────────────────────────────────────────────── constexpr int VERSION = 1; constexpr int LOW_STOCK = 5; // threshold for "low stock" warning constexpr double TAX_RATE = 0.08; // 8% sales tax using ID = unsigned int; using Qty = int; using Price = double; // ── ENUM CLASS ──────────────────────────────────────────────────────────── enum class Category { Electronics, Food, Clothing, Tools, Other }; std::string categoryName(Category c) { switch (c) { case Category::Electronics: return "Electronics"; case Category::Food: return "Food"; case Category::Clothing: return "Clothing"; case Category::Tools: return "Tools"; default: return "Other"; } } // ── CUSTOM EXCEPTION ───────────────────────────────────────────────────── class InventoryError : public std::runtime_error { private: ID _itemId; // 0 = not item-specific public: InventoryError(const std::string& msg, ID id = 0) : std::runtime_error(msg), _itemId{id} {} ID itemId() const noexcept { return _itemId; } }; // ── TEMPLATE UTILITY: clamp a value ────────────────────────────────────── template<typename T> T clamp(T val, T lo, T hi) { return val < lo ? lo : (val > hi ? hi : val); } // ── TEMPLATE UTILITY: formatted string ─────────────────────────────────── template<typename... Args> std::string fmt(Args&&... args) { std::ostringstream oss; (oss << ... << std::forward<Args>(args)); // C++17 fold expression return oss.str(); } // ═══════════════════════════════════════════════════════════════ // BASE CLASS (abstract — cannot instantiate directly) // ═══════════════════════════════════════════════════════════════ class Item { protected: // accessible by derived classes static ID _nextId; // shared counter across all Items ID _id; std::string _name; Price _price; Qty _qty; Category _cat; public: // Member initializer list — preferred constructor style Item(std::string name, Price price, Qty qty, Category cat) : _id{_nextId++} , _name{std::move(name)} // move: avoids string copy , _price{price} , _qty{qty} , _cat{cat} { if (price < 0) throw InventoryError("Negative price"); if (qty < 0) throw InventoryError("Negative quantity"); } // Virtual destructor — REQUIRED for polymorphic base classes virtual ~Item() = default; // ── Getters (const methods) ────────────────────────────────── ID id() const { return _id; } const std::string& name() const { return _name; } Price price() const { return _price; } Qty qty() const { return _qty; } Category category() const { return _cat; } bool isLow() const { return _qty <= LOW_STOCK; } // ── Setters (with validation) ───────────────────────────────── void restock(Qty amount) { if (amount <= 0) throw InventoryError("Restock amount must be > 0", _id); _qty += amount; } void setPrice(Price p) { if (p < 0) throw InventoryError("Price cannot be negative", _id); _price = p; } // ── Pure virtual: every derived class MUST implement ───────── virtual std::string typeLabel() const = 0; virtual std::string extraInfo() const = 0; // ── Virtual with default — derived classes CAN override ────── virtual Price taxedPrice() const { return _price * (1.0 + TAX_RATE); } // ── Operator overloading ───────────────────────────────────── bool operator<(const Item& o) const { return _name < o._name; } bool operator==(const Item& o) const { return _id == o._id; } // ── Friend: allows stream operator to access private members ─ friend std::ostream& operator<<(std::ostream& os, const Item& item) { os << std::setw(4) << item._id << std::setw(22) << std::left << item._name << std::setw(14) << std::right << item.typeLabel() << std::setw(10) << std::fixed << std::setprecision(2) << item._price << std::setw(8) << item._qty << (item.isLow() ? " ⚠ LOW" : ""); return os; } }; ID Item::_nextId = 1000; // define static member outside class // ═══════════════════════════════════════════════════════════════ // DERIVED CLASS 1 — Electronic // ═══════════════════════════════════════════════════════════════ class Electronic : public Item { private: int _warrantyMonths; std::string _brand; public: Electronic(std::string name, Price p, Qty q, std::string brand, int warranty = 12) : Item{std::move(name), p, q, Category::Electronics} , _warrantyMonths{warranty} , _brand{std::move(brand)} {} std::string typeLabel() const override { return "[Electronic]"; } std::string extraInfo() const override { return fmt("Brand: ", _brand, " Warranty: ", _warrantyMonths, "mo"); } // Electronics are tax-exempt in this example Price taxedPrice() const override { return _price; } }; // ═══════════════════════════════════════════════════════════════ // DERIVED CLASS 2 — PerishableItem (Food with expiry) // ═══════════════════════════════════════════════════════════════ class Perishable : public Item { private: int _daysToExpiry; public: Perishable(std::string name, Price p, Qty q, int days) : Item{std::move(name), p, q, Category::Food} , _daysToExpiry{days} { if (days < 0) throw InventoryError("Expiry days cannot be negative"); } std::string typeLabel() const override { return "[Perishable]"; } std::string extraInfo() const override { return fmt("Expires in: ", _daysToExpiry, " days"); } bool isExpiringSoon() const { return _daysToExpiry <= 7; } int daysToExpiry() const { return _daysToExpiry; } }; // ═══════════════════════════════════════════════════════════════ // DERIVED CLASS 3 — Tool // ═══════════════════════════════════════════════════════════════ class Tool : public Item { private: std::string _material; public: Tool(std::string name, Price p, Qty q, std::string mat) : Item{std::move(name), p, q, Category::Tools} , _material{std::move(mat)} {} std::string typeLabel() const override { return "[Tool]"; } std::string extraInfo() const override { return fmt("Material: ", _material); } }; // ═══════════════════════════════════════════════════════════════ // INVENTORY CLASS — manages the collection // ═══════════════════════════════════════════════════════════════ class Inventory { private: // vector of unique_ptr — polymorphic collection, auto-managed memory std::vector<std::unique_ptr<Item>> _items; // Helper: find iterator by ID auto findById(ID id) { return std::find_if(_items.begin(), _items.end(), [id](const auto& p){ return p->id() == id; }); } public: // ── Add item (move into collection) ────────────────────────── void add(std::unique_ptr<Item> item) { _items.push_back(std::move(item)); // move — no copy } // ── Remove item by ID ───────────────────────────────────────── bool remove(ID id) { auto it = findById(id); if (it == _items.end()) return false; _items.erase(it); return true; } // ── Search by name (case-insensitive partial match) ─────────── std::vector<const Item*> search(const std::string& query) const { std::vector<const Item*> results; auto toLower = [](char c){ return static_cast<char>(std::tolower(c)); }; std::string q; std::transform(query.begin(), query.end(), std::back_inserter(q), toLower); for (const auto& item : _items) { std::string name; std::transform(item->name().begin(), item->name().end(), std::back_inserter(name), toLower); if (name.find(q) != std::string::npos) results.push_back(item.get()); } return results; } // ── Get item by ID (returns optional) ──────────────────────── std::optional<Item*> getById(ID id) { auto it = findById(id); if (it == _items.end()) return std::nullopt; return it->get(); } // ── Sort items by a user-supplied comparator ────────────────── void sortBy(std::function<bool(const Item*, const Item*)> cmp) { std::sort(_items.begin(), _items.end(), [&cmp](const auto& a, const auto& b){ return cmp(a.get(), b.get()); }); } // ── Statistics (tuple return) ───────────────────────────────── std::tuple<int, double, int> stats() const { int totalQty = std::accumulate(_items.begin(), _items.end(), 0, [](int sum, const auto& p){ return sum + p->qty(); }); double totalVal = std::accumulate(_items.begin(), _items.end(), 0.0, [](double sum, const auto& p){ return sum + p->price() * p->qty(); }); int lowCount = static_cast<int>( std::count_if(_items.begin(), _items.end(), [](const auto& p){ return p->isLow(); })); return {totalQty, totalVal, lowCount}; } // ── Export to text file ─────────────────────────────────────── void exportToFile(const std::string& filename) const { std::ofstream out{filename}; if (!out) throw InventoryError(fmt("Cannot write: ", filename)); out << "ID NAME TYPE PRICE QTY\n" << std::string(65, '-') << "\n"; for (const auto& item : _items) { out << *item << "\n" << " " << item->extraInfo() << "\n"; } auto [qty, val, low] = stats(); // C++17 structured binding out << std::string(65, '=') << "\n" << "Total items: " << _items.size() << " Total qty: " << qty << " Value: $" << std::fixed << std::setprecision(2) << val << " Low stock: " << low << "\n"; } // ── Category breakdown ──────────────────────────────────────── std::map<std::string, int> categoryReport() const { std::map<std::string, int> report; for (const auto& item : _items) ++report[categoryName(item->category())]; return report; } // Range accessors for external iteration auto begin() const { return _items.begin(); } auto end() const { return _items.end(); } size_t size() const { return _items.size(); } bool empty()const { return _items.empty(); } }; } // namespace Inv // ═══════════════════════════════════════════════════════════════ // UI HELPERS — free functions // ═══════════════════════════════════════════════════════════════ using namespace Inv; void printHeader() { std::cout << "\n" << " ╔══════════════════════════════════════╗\n" << " ║ INVENTORY MANAGER v" << VERSION << " ║\n" << " ╚══════════════════════════════════════╝\n"; } void printMenu() { std::cout << " a) Add item l) List all s) Search\n" " r) Restock p) Report e) Export to file\n" " q) Quit\n" " Command: "; } void printSeparator(char c = '-', int w = 65) { std::cout << " " << std::string(w, c) << "\n"; } void listItems(const Inventory& inv, const std::vector<const Item*>* filter = nullptr) { if (inv.empty() && !filter) { std::cout << " (no items)\n"; return; } std::cout << " ID NAME TYPE PRICE QTY\n"; printSeparator(); const auto& source = filter ? *filter : [&]() -> std::vector<const Item*> { std::vector<const Item*> all; for (const auto& p : inv) all.push_back(p.get()); return all; }(); for (const auto* item : source) { std::cout << " " << *item << "\n"; // calls operator<< (virtual dispatch) std::cout << " " << item->extraInfo() << "\n"; // virtual } printSeparator(); } // ── getInput: read a line and strip leading/trailing whitespace ── std::string getInput(const std::string& prompt = "") { if (!prompt.empty()) std::cout << " " << prompt << ": "; std::string line; std::getline(std::cin, line); // trim whitespace with iterators auto start = std::find_if_not(line.begin(), line.end(), [](char c){ return std::isspace(c); }); auto end = std::find_if_not(line.rbegin(), line.rend(), [](char c){ return std::isspace(c); }).base(); return (start < end) ? std::string(start, end) : ""; } // ── getNumber: validated numeric input ────────────────────────── template<typename T> T getNumber(const std::string& prompt, T lo, T hi) { while (true) { auto s = getInput(prompt); try { T val; if constexpr (std::is_integral_v<T>) // C++17 if constexpr val = static_cast<T>(std::stoll(s)); else val = static_cast<T>(std::stod(s)); if (val < lo || val > hi) throw std::out_of_range(""); return val; } catch (...) { std::cout << " Enter a number between " << lo << " and " << hi << "\n"; } } } // ── Choose item type with do-while ─────────────────────────────── std::unique_ptr<Item> createItem() { std::cout << " Type: 1=Electronic 2=Perishable 3=Tool: "; int type = getNumber<int>("", 1, 3); auto name = getInput("Name"); auto price = getNumber<double>("Price", 0.0, 999999.0); auto qty = getNumber<int> ("Quantity", 0, 99999); switch (type) { case 1: { auto brand = getInput("Brand"); auto warranty = getNumber<int>("Warranty (months)", 0, 120); return std::make_unique<Electronic>(name, price, qty, brand, warranty); } case 2: { auto days = getNumber<int>("Days to expiry", 0, 3650); return std::make_unique<Perishable>(name, price, qty, days); } default: { auto mat = getInput("Material"); return std::make_unique<Tool>(name, price, qty, mat); } } } // ═══════════════════════════════════════════════════════════════ // MAIN // ═══════════════════════════════════════════════════════════════ int main() { printHeader(); Inventory inv; // Seed with sample data to explore on first run inv.add(std::make_unique<Electronic>("USB-C Hub", 49.99, 12, "Anker", 24)); inv.add(std::make_unique<Electronic>("Mechanical KB", 129.0, 3, "Keychron", 12)); inv.add(std::make_unique<Perishable>("Greek Yogurt", 2.49, 40, 14)); inv.add(std::make_unique<Perishable>("Sourdough", 4.99, 4, 5)); // low qty inv.add(std::make_unique<Tool> ("Claw Hammer", 18.00, 8, "Steel")); inv.add(std::make_unique<Tool> ("Torque Wrench",65.00, 2, "Chrome-V")); // low bool running = true; while (running) { std::cout << "\n"; printMenu(); auto cmd = getInput(); if (cmd.empty()) continue; // ── Command dispatch ───────────────────────────────────── try { switch (cmd[0]) { case 'a': { // ADD auto item = createItem(); std::cout << " Added: " << *item << "\n"; inv.add(std::move(item)); break; } case 'l': { // LIST — sort by name first inv.sortBy([](const Item* a, const Item* b){ return a->name() < b->name(); }); listItems(inv); break; } case 's': { // SEARCH auto q = getInput("Search term"); auto results = inv.search(q); std::cout << " Found " << results.size() << " result(s):\n"; listItems(inv, &results); break; } case 'r': { // RESTOCK auto id = getNumber<int>("Item ID", 1000, 99999); if (auto opt = inv.getById(static_cast<ID>(id)); opt) { auto qty = getNumber<int>("Add quantity", 1, 10000); (*opt)->restock(qty); std::cout << " Restocked. New qty: " << (*opt)->qty() << "\n"; } else { std::cout << " Item " << id << " not found.\n"; } break; } case 'p': { // REPORT — structured binding auto [qty, val, low] = inv.stats(); std::cout << "\n ── Summary Report ──────────────────────\n" << " Total SKUs : " << inv.size() << "\n" << " Total Units : " << qty << "\n" << " Total Value : $" << std::fixed << std::setprecision(2) << val << "\n" << " Low Stock : " << low << " item(s)\n" << " ── By Category ───────────────────────\n"; for (const auto& [cat, count] : inv.categoryReport()) std::cout << " " << std::setw(14) << std::left << cat << count << "\n"; break; } case 'e': { // EXPORT auto fname = getInput("Filename [inventory.txt]"); if (fname.empty()) fname = "inventory.txt"; inv.exportToFile(fname); std::cout << " Exported to: " << fname << "\n"; break; } case 'q': // QUIT running = false; std::cout << " Goodbye!\n"; break; default: std::cout << " Unknown command. Press Enter to see menu.\n"; } } // ── Catch custom and standard exceptions separately ─────── catch (const InventoryError& e) { std::cout << " [Inventory Error] " << e.what(); if (e.itemId()) std::cout << " (item " << e.itemId() << ")"; std::cout << "\n"; } catch (const std::exception& e) { std::cout << " [Error] " << e.what() << "\n"; } } return 0; }
| Code Element | C++ Concept | Why It Matters |
|---|---|---|
| namespace Inv { ... } | Namespaces | Avoids name collisions in larger programs |
| enum class Category | Scoped enum | Strongly typed — can't accidentally mix with int |
| class InventoryError : public runtime_error | Custom exception | Domain-specific errors with extra data (itemId) |
| template<typename... Args> fmt() | Variadic template + fold expression | Type-safe string building without sprintf |
| : _id{_nextId++}, _name{std::move(name)} | Member initializer list + move | Efficient construction; avoid unnecessary copies |
| virtual double taxedPrice() const = 0 | Pure virtual / abstract class | Forces all subclasses to implement; enables polymorphism |
| double taxedPrice() const override | override keyword | Compiler error if signature doesn't match base — safer |
| friend ostream& operator<< | Operator overloading + friend | std::cout << item works naturally |
| vector<unique_ptr<Item>> | Polymorphic collection + smart ptr | Stores any Item subtype; auto-deletes; no raw new/delete |
| std::move(item) | Move semantics | Transfers ownership into vector without copying the object |
| optional<Item*> getById() | std::optional | Expresses "may not exist" without null pointers or exceptions |
| auto [qty, val, low] = stats() | Structured bindings (C++17) | Unpack tuple return values cleanly |
| if constexpr (is_integral_v<T>) | if constexpr (C++17) | Compile-time branching inside templates |
| sortBy(std::function<bool(...)>) | std::function + lambda | Caller passes any sorting criterion as a function object |
| std::accumulate(..., lambda) | STL algorithm + lambda | Sum/aggregate in one line — no manual loop |
After mastering this guide: learn move semantics (rvalue references, &&) in depth, explore concurrency with <thread> and <mutex>, study SFINAE and type traits for advanced template programming, and read the C++ Core Guidelines (isocpp.github.io/CppCoreGuidelines) — the definitive style guide endorsed by Bjarne Stroustrup himself.