The Birth of C++

C++ was created by Bjarne Stroustrup at Bell Labs in 1979, initially called "C with Classes". It was officially renamed C++ in 1983. The name itself is a programmer's joke — ++ is the increment operator in C, implying C++ is "one step above C".

Why Was C++ Created?
  • Simulate complex systems — Stroustrup needed to simulate distributed computing for his PhD thesis
  • C lacked abstraction — C was powerful but had no way to model real-world entities cleanly
  • Simula had the right ideas — Object-oriented concepts from Simula were too slow for systems programming
  • Goal: Combine the efficiency of C with the abstraction of Simula
Key Milestones
YearVersionKey Additions
1979C with ClassesClasses, basic inheritance
1983C++Virtual functions, function overloading
1998C++98First ISO standard, STL, templates
2003C++03Bug fixes for C++98
2011C++11Move semantics, lambdas, auto, nullptr
2014C++14Generic lambdas, binary literals
2017C++17std::optional, structured bindings, if constexpr
2020C++20Concepts, ranges, coroutines, modules
2023C++23std::expected, std::print, stacktrace
Where C++ Is Used Today
  • Operating Systems — Windows, parts of macOS/Linux kernels
  • Game Engines — Unreal Engine, Unity runtime, Godot
  • Databases — MySQL, MongoDB, Redis
  • Browsers — Chrome (V8 engine), Firefox (SpiderMonkey)
  • Embedded Systems — Firmware, automotive software, IoT
  • Finance — High-frequency trading, risk systems
  • Machine Learning — TensorFlow, PyTorch backends

C++ Fundamentals

Hello World & Program Structure
#include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }
Data Types
TypeSizeRangeExample
int4 bytes-2³¹ to 2³¹-1int x = 42;
long long8 bytes-2⁶³ to 2⁶³-1long long x = 9e18;
float4 bytes~7 digitsfloat f = 3.14f;
double8 bytes~15 digitsdouble d = 3.14159;
char1 byte-128 to 127char c = 'A';
bool1 bytetrue/falsebool b = true;
size_t8 bytes0 to 2⁶⁴-1size_t n = arr.size();
Variable Declaration & Initialization
// C-style initialization int a = 10; // Constructor initialization int b(20); // Uniform initialization (C++11) — preferred int c{30}; // auto — compiler deduces type auto x = 42; // int auto y = 3.14; // double auto s = "hello"; // const char* // const — immutable const int MAX = 100; // constexpr — compile-time constant (C++11) constexpr double PI = 3.14159265358979;
Control Flow
// Range-based for loop (C++11) vector<int> nums = {1, 2, 3, 4, 5}; for (auto& n : nums) { cout << n << " "; } // if with initializer (C++17) if (auto it = myMap.find("key"); it != myMap.end()) { cout << it->second; } // Structured switch switch (value) { case 1: cout << "one"; break; case 2: cout << "two"; break; default: cout << "other"; }
Functions
// Basic function int add(int a, int b) { return a + b; } // Default parameters void greet(string name, string msg = "Hello") { cout << msg << " " << name; } // Function overloading double area(double r) { return 3.14159 * r * r; } double area(double l, double w) { return l * w; } // Inline function (hint to compiler) inline int square(int x) { return x * x; } // Pass by reference (avoids copy) void swap(int& a, int& b) { int temp = a; a = b; b = temp; }
Arrays & Strings
// C-style array int arr[5] = {1, 2, 3, 4, 5}; // std::array (C++11) — fixed size, bounds checked in debug #include <array> array<int, 5> a = {1, 2, 3, 4, 5}; a.at(2); // bounds-checked access a.size(); // 5 // std::string #include <string> string s = "Hello"; s += " World"; s.length(); // 11 s.substr(0, 5); // "Hello" s.find("World"); // 6 s.replace(6, 5, "C++"); // "Hello C++"

Object-Oriented Programming in C++

Classes & Objects
class BankAccount { private: // Encapsulation string owner; double balance; public: // Constructor BankAccount(string owner, double bal) : owner(owner), balance(bal) {} // initializer list // Destructor ~BankAccount() { cout << "Account closed" << endl; } void deposit(double amount) { balance += amount; } void withdraw(double amount) { if (amount > balance) throw runtime_error("Insufficient funds"); balance -= amount; } double getBalance() const { return balance; } // const method };
Inheritance
class Animal { public: string name; Animal(string n) : name(n) {} virtual void speak() const { // virtual for polymorphism cout << name << " makes a sound"; } virtual ~Animal() {} // always virtual destructor }; class Dog : public Animal { // public inheritance public: Dog(string n) : Animal(n) {} void speak() const override { // override keyword (C++11) cout << name << ": Woof!"; } }; // Polymorphism via pointer/reference Animal* a = new Dog("Rex"); a->speak(); // "Rex: Woof!" — runtime dispatch delete a;
Inheritance Types
TypePublic members becomeProtected members become
publicpublicprotected
protectedprotectedprotected
privateprivateprivate
Abstract Classes & Pure Virtual
class Shape { // Abstract class public: virtual double area() const = 0; // pure virtual virtual double perimeter() const = 0; virtual ~Shape() = default; }; class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } double perimeter() const override { return 2 * 3.14159 * radius; } };
Operator Overloading
class Vector2D { public: double x, y; Vector2D(double x, double y) : x(x), y(y) {} // Operator overload Vector2D operator+(const Vector2D& other) const { return {x + other.x, y + other.y}; } // Stream output friend ostream& operator<<(ostream& os, const Vector2D& v) { return os << "(" << v.x << ", " << v.y << ")"; } }; Vector2D a(1, 2), b(3, 4); Vector2D c = a + b; cout << c; // (4, 6)
The Rule of Three / Five / Zero
// Rule of Five — if you define any of these, define all five: class Buffer { int* data; size_t size; public: Buffer(size_t n) : data(new int[n]), size(n) {} ~Buffer() { delete[] data; } // 1. Destructor Buffer(const Buffer& o) { /* deep copy */ } // 2. Copy ctor Buffer& operator=(const Buffer& o) { /* copy assign */ }// 3. Copy assign Buffer(Buffer&& o) noexcept { /* move ctor */ } // 4. Move ctor Buffer& operator=(Buffer&& o) noexcept { /* move assign */ }// 5. Move assign // Rule of Zero: use RAII types (vector, unique_ptr) and need none of the above };

Manual Memory Management in C++

Stack vs Heap
FeatureStackHeap
AllocationAutomatic (LIFO)Manual (new/delete)
SpeedVery fastSlower (allocation overhead)
SizeSmall (~1-8 MB)Large (GBs)
LifetimeScope-boundUntil explicitly freed
ErrorStack overflowMemory leak / dangling ptr
new and delete
// Single object int* p = new int(42); delete p; p = nullptr; // good practice — avoids dangling pointer // Array int* arr = new int[10]; arr[0] = 1; delete[] arr; // MUST use delete[] for arrays // Common mistakes int* x = new int(10); delete x; // delete x; // double delete — undefined behaviour! // *x = 5; // use-after-free — undefined behaviour!
RAII — Resource Acquisition Is Initialization
// RAII: tie resource lifetime to object lifetime class FileHandle { FILE* fp; public: FileHandle(const char* path) : fp(fopen(path, "r")) { if (!fp) throw runtime_error("Cannot open file"); } ~FileHandle() { if (fp) fclose(fp); } // guaranteed cleanup // Prevent copying FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; }; // Usage — destructor auto-called even if exception thrown { FileHandle f("data.txt"); // use f... } // file closed here automatically
Memory Layout of a C++ Object
class Derived : public Base { // Memory layout: // [vptr] ← virtual table pointer (if virtual methods) // [Base data members] // [Derived data members] };

Common Memory Bugs: Memory leak (forgetting delete), dangling pointer (delete then use), double free (delete twice), buffer overflow (writing past array bounds). Smart pointers eliminate most of these.

Pointers & References

Pointers Basics
int val = 42; int* ptr = &val; // ptr stores address of val cout << ptr; // prints address e.g. 0x7ffee cout << *ptr; // dereference: prints 42 *ptr = 100; // modifies val through pointer // Pointer arithmetic int arr[] = {10, 20, 30}; int* p = arr; p++; // now points to arr[1] cout << *p; // 20 // nullptr — type-safe null (C++11) int* np = nullptr; if (np == nullptr) cout << "null pointer";
Pointer to Pointer, const Pointers
int x = 10; int* p = &x; int** pp = &p; // pointer to pointer cout << **pp; // 10 const int* cp = &x; // pointer to const — can't modify value int* const pc = &x; // const pointer — can't change address const int* const cpc = &x; // both const
References
int a = 10; int& ref = a; // ref IS a — same memory ref = 20; // a is now 20 // Key differences vs pointer: // ✓ References must be initialized // ✓ References cannot be reseated (always refer to same object) // ✓ No null reference (safer) // ✓ No pointer arithmetic // lvalue reference int& lref = a; // rvalue reference (C++11) — binds to temporaries int&& rref = 42; int&& moved = std::move(a); // cast to rvalue
Move Semantics (C++11)
class MyVec { int* data; size_t n; public: // Move constructor — steals resources, leaves source empty MyVec(MyVec&& other) noexcept : data(other.data), n(other.n) { other.data = nullptr; // source is now empty other.n = 0; } }; vector<string> v1 = {"a", "b", "c"}; vector<string> v2 = std::move(v1); // O(1) — no copy! // v1 is now in valid but unspecified state
Function Pointers & std::function
// Function pointer int (*fp)(int, int) = add; fp(3, 4); // 7 // std::function — wraps any callable (C++11) #include <functional> function<int(int, int)> f = add; f = [](int a, int b) { return a * b; }; // reassign to lambda f(3, 4); // 12

Smart Pointers (C++11)

Smart pointers are RAII wrappers around raw pointers. They automatically manage memory — no manual delete needed. Defined in <memory>.

unique_ptr — Exclusive Ownership
#include <memory> // unique_ptr — one owner, auto-deletes when out of scope unique_ptr<int> p = make_unique<int>(42); // preferred (C++14) cout << *p; // 42 cout << p.get(); // raw pointer address // Cannot copy — only move // unique_ptr<int> p2 = p; // ERROR — no copy unique_ptr<int> p2 = move(p); // OK — p is now nullptr // Custom deleter unique_ptr<FILE, decltype(&fclose)> fp(fopen("x.txt", "r"), fclose); // With arrays unique_ptr<int[]> arr = make_unique<int[]>(10); arr[0] = 1; // Release raw pointer (ownership transferred — you must delete) int* raw = p2.release(); delete raw; // Reset (delete current, optionally take new) p2.reset(new int(99)); p2.reset(); // p2 is now nullptr

Rule: Prefer unique_ptr by default. It has zero overhead compared to a raw pointer and makes ownership explicit.

shared_ptr — Shared Ownership
// shared_ptr — reference counted, multiple owners shared_ptr<int> sp1 = make_shared<int>(42); shared_ptr<int> sp2 = sp1; // copy is allowed — ref count = 2 shared_ptr<int> sp3 = sp1; // ref count = 3 cout << sp1.use_count(); // 3 cout << *sp1; // 42 sp2.reset(); // ref count = 2, memory NOT freed sp3.reset(); // ref count = 1, memory NOT freed // sp1 goes out of scope → count=0 → memory freed // Aliasing constructor — share ownership, point to different thing shared_ptr<int> alias(sp1, &sp1->someField);
weak_ptr — Non-Owning Observer
// weak_ptr — does NOT affect ref count // Used to break circular references shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp = sp; cout << wp.use_count(); // 1 (sp only, wp doesn't count) cout << wp.expired(); // false — sp still alive // Must lock before using if (auto locked = wp.lock()) { // returns shared_ptr or empty cout << *locked; // 100 } sp.reset(); // object destroyed cout << wp.expired(); // true
Circular Reference Problem
// PROBLEM — memory leak with shared_ptr cycle struct Node { shared_ptr<Node> next; // cycle: each holds the other → never freed }; // SOLUTION — break cycle with weak_ptr struct Node { weak_ptr<Node> next; // weak: doesn't prevent destruction };
Comparison Table
Featureunique_ptrshared_ptrweak_ptr
OwnershipExclusiveSharedNone
Copy❌ (move only)
Ref countNo overheadAtomic countDoesn't increment
Use forDefault choiceShared resourcesCaches, observer pattern
Thread safe?No (by design)Ref count yes, data noSame as shared_ptr

Interview Tip: Always prefer make_unique and make_shared over new. They're exception-safe and, for shared_ptr, allocate the control block and object together (better cache performance).

Standard Template Library

Sequence Containers
// vector — dynamic array, O(1) random access vector<int> v = {1, 2, 3}; v.push_back(4); // O(1) amortized v.emplace_back(5); // construct in-place, faster v.pop_back(); v.insert(v.begin(), 0); // O(n) v.erase(v.begin()); // O(n) // deque — double-ended queue deque<int> dq; dq.push_front(1); // O(1) dq.push_back(2); // O(1) dq.pop_front(); // list — doubly linked list, O(1) insert/erase at any position list<int> lst = {1, 2, 3}; lst.push_front(0); lst.sort(); lst.reverse();
Associative Containers
// set — sorted unique elements, O(log n) set<int> s = {3, 1, 2}; s.insert(4); s.count(2); // 1 (exists) or 0 s.erase(1); // map — sorted key-value pairs, O(log n) map<string, int> m; m["Alice"] = 25; m.insert({"Bob", 30}); m.find("Alice")->second; for (auto& [k, v] : m) cout << k << ":" << v; // C++17 structured binding // unordered_map — hash map, O(1) average unordered_map<string, int> um; um.reserve(100); // preallocate buckets um["x"] = 1; um.count("x"); // 1
Container Adaptors
// stack — LIFO stack<int> st; st.push(1); st.push(2); st.top(); // 2 st.pop(); // queue — FIFO queue<int> q; q.push(1); q.push(2); q.front(); // 1 q.pop(); // priority_queue — max-heap by default priority_queue<int> pq; pq.push(3); pq.push(1); pq.push(4); pq.top(); // 4 // min-heap priority_queue<int, vector<int>, greater<int>> minpq;
STL Algorithms
#include <algorithm> vector<int> v = {5, 3, 1, 4, 2}; sort(v.begin(), v.end()); // ascending sort(v.begin(), v.end(), greater<int>()); // descending binary_search(v.begin(), v.end(), 3); // true (sorted) lower_bound(v.begin(), v.end(), 3); // iterator to 3 auto it = find(v.begin(), v.end(), 4); // iterator or end() count(v.begin(), v.end(), 3); // occurrences reverse(v.begin(), v.end()); max_element(v.begin(), v.end()); min_element(v.begin(), v.end()); // Transform transform(v.begin(), v.end(), v.begin(), [](int x) { return x * 2; }); // accumulate (sum) #include <numeric> int sum = accumulate(v.begin(), v.end(), 0);
Iterators
// Iterator types: // InputIterator, OutputIterator, ForwardIterator, // BidirectionalIterator, RandomAccessIterator vector<int> v = {1, 2, 3}; auto it = v.begin(); // iterator to first auto end = v.end(); // past-the-end ++it; // advance *it; // dereference = 2 // Reverse iterator for (auto rit = v.rbegin(); rit != v.rend(); ++rit) cout << *rit; // 3 2 1

Templates — Generic Programming

Function Templates
// Generic max function template<typename T> T maxVal(T a, T b) { return a > b ? a : b; } maxVal(3, 5); // int version generated maxVal(3.14, 2.71); // double version generated maxVal<string>("a", "b"); // explicit type // Multiple template parameters template<typename T, typename U> auto add(T a, U b) { return a + b; } // return type deduced
Class Templates
template<typename T> class Stack { vector<T> data; public: void push(const T& val) { data.push_back(val); } void pop() { data.pop_back(); } T& top() { return data.back(); } bool empty() const { return data.empty(); } }; Stack<int> si; Stack<string> ss;
Template Specialization
// Primary template template<typename T> bool isZero(T x) { return x == T{}; } // Full specialization for double (floating point comparison) template<> bool isZero<double>(double x) { return abs(x) < 1e-9; }
Variadic Templates (C++11)
// Accept any number of arguments of any type template<typename... Args> void print(Args... args) { (cout << ... << args) << "\n"; // fold expression (C++17) } print(1, " hello ", 3.14, true); // 1 hello 3.14 1 // Perfect forwarding template<typename T, typename... Args> unique_ptr<T> make(Args&&... args) { return unique_ptr<T>(new T(forward<Args>(args)...)); }

Exception Handling in C++

Try-Catch-Throw
try { int result = divide(10, 0); } catch (const invalid_argument& e) { cerr << "Invalid arg: " << e.what(); } catch (const runtime_error& e) { cerr << "Runtime error: " << e.what(); } catch (const exception& e) { cerr << "Exception: " << e.what(); } catch (...) { cerr << "Unknown exception"; }
Standard Exception Hierarchy
std::exception ├── std::logic_error │ ├── invalid_argument │ ├── out_of_range │ └── length_error ├── std::runtime_error │ ├── overflow_error │ ├── underflow_error │ └── range_error └── std::bad_alloc ← new fails std::bad_cast ← dynamic_cast fails
Custom Exceptions
class NetworkError : public std::runtime_error { int errorCode; public: NetworkError(const string& msg, int code) : std::runtime_error(msg), errorCode(code) {} int getCode() const { return errorCode; } }; throw NetworkError("Connection refused", 403);
noexcept Specifier
// noexcept — promise that function won't throw void safeOperation() noexcept { // if throw happens here, std::terminate() is called } // Move operations should be noexcept for optimal STL performance MyClass(MyClass&& other) noexcept = default;

Best Practice: Always catch exceptions by const reference (catch (const std::exception& e)). Never catch by value — it causes object slicing.

C++11 — The Modern Revolution

Lambda Expressions
// Syntax: [capture](params) -> return_type { body } auto add = [](int a, int b) { return a + b; }; add(3, 4); // 7 // Captures int x = 10; auto f1 = [x]() { return x; }; // capture x by value auto f2 = [&x]() { x++; }; // capture x by reference auto f3 = [=]() { return x; }; // capture all by value auto f4 = [&]() { x++; }; // capture all by reference auto f5 = [this]() { return member; }; // capture this // With STL vector<int> v = {5, 2, 8, 1}; sort(v.begin(), v.end(), [](int a, int b) { return a > b; // descending }); // Generic lambda (C++14) auto multiply = [](auto a, auto b) { return a * b; };
auto & decltype
auto x = 42; // int auto v = vector<int>(); // vector<int> const auto& ref = x; // const int& // decltype — query type of expression int a; double b; decltype(a + b) c; // double (type of a+b) // Trailing return type template<typename A, typename B> auto add(A a, B b) -> decltype(a + b) { return a + b; }
Initializer Lists & Uniform Initialization
// Uniform init — works for all types int x{5}; vector<int> v{1, 2, 3}; MyClass obj{arg1, arg2}; // std::initializer_list void print(initializer_list<int> list) { for (int x : list) cout << x << " "; } print({1, 2, 3, 4, 5});
enum class (Scoped Enums)
// Old: unscoped, pollutes namespace enum Color { Red, Green, Blue }; // New: scoped, type-safe enum class Color { Red, Green, Blue }; Color c = Color::Red; // must qualify // int x = c; // ERROR — no implicit conversion int x = static_cast<int>(c); // explicit cast needed
nullptr, override, final, delete, default
// nullptr — type-safe null pointer int* p = nullptr; // not NULL or 0 // override — compiler checks you are overriding void speak() const override {} // final — prevent further overriding or inheritance class Sealed final {}; virtual void doSomething() final; // delete — disable function generation MyClass(const MyClass&) = delete; // no copies // default — explicitly request compiler-generated version MyClass() = default;
range-based for, tuple, tie
// Range-based for for (const auto& x : container) {} // tuple #include <tuple> auto t = make_tuple(1, "hello", 3.14); get<0>(t); // 1 get<1>(t); // "hello" // tie — unpack tuple int a; string b; double c; tie(a, b, c) = t;

C++17 — Key Features Explained

1. Structured Bindings

Unpack any struct, array, or tuple directly into named variables — no more get<0>().

// Unpack pair map<string, int> scores = {{"Alice", 95}, {"Bob", 87}}; for (const auto& [name, score] : scores) { cout << name << ": " << score << "\n"; } // Unpack struct struct Point { int x, y; }; Point p{3, 4}; auto [x, y] = p; // x=3, y=4 // Unpack array int arr[] = {1, 2, 3}; auto& [a, b, c] = arr;
2. std::optional

Represents a value that may or may not be present — a type-safe alternative to returning -1, 0, or nullptr as sentinel values.

#include <optional> // Return optional instead of magic values optional<int> findIndex(vector<int>& v, int target) { for (int i = 0; i < v.size(); i++) if (v[i] == target) return i; return nullopt; // nothing found } auto idx = findIndex(v, 42); if (idx.has_value()) { cout << *idx; // dereference cout << idx.value(); // same } idx.value_or(-1); // default if empty
3. std::variant

A type-safe union — holds exactly one value from a list of types. Safer than C-style unions.

#include <variant> variant<int, double, string> v = 42; v = "hello"; // reassign to different type get<string>(v); // "hello" // get<int>(v); // throws bad_variant_access get_if<int>(&v); // nullptr (safe) v.index(); // 2 (index of active type) holds_alternative<string>(v); // true // Visit — pattern matching style visit([](auto&& val) { cout << val; }, v);
4. std::any

Holds a value of any type — like a void* but type-safe.

#include <any> any a = 42; a = "hello"; a = vector<int>{1, 2, 3}; any_cast<vector<int>>(a); // extract value a.has_value(); // true a.type().name(); // type name
5. if constexpr

Compile-time if statement. Eliminates template specializations in many cases. Unused branch is not compiled — removes the need for tag dispatch or SFINAE.

template<typename T> void process(T val) { if constexpr (is_integral_v<T>) { cout << "integer: " << val; } else if constexpr (is_floating_point_v<T>) { cout << "float: " << val; } else { cout << "other: " << val; } } // Each instantiation only compiles the matching branch
6. if/switch with Initializer
// Scope variable inside if/switch if (auto it = m.find("key"); it != m.end()) { cout << it->second; // it scoped to this if-else } switch (auto status = getStatus(); status) { case 200: break; case 404: break; }
7. Fold Expressions
// Sum all variadic args template<typename... Args> auto sum(Args... args) { return (args + ...); } sum(1, 2, 3, 4); // 10 // Print all args template<typename... Args> void print(Args... args) { (cout << ... << args); // left fold over << }
8. std::string_view

A non-owning reference to a string — avoids copying. Use for read-only string parameters instead of const string&.

#include <string_view> // No copy — just a view into existing string void process(string_view sv) { cout << sv.length(); cout << sv.substr(0, 3); } process("literal"); // no allocation process(myString); // no copy process(myString.substr(0, 5)); // no extra copy
9. std::filesystem
#include <filesystem> namespace fs = filesystem; fs::path p = "/home/user/data.txt"; p.filename(); // "data.txt" p.extension(); // ".txt" p.parent_path(); // "/home/user" fs::exists(p); fs::is_directory(p); fs::create_directory("newdir"); fs::copy("src.txt", "dst.txt"); fs::remove("old.txt"); // Iterate directory for (const auto& entry : fs::directory_iterator(".")) { cout << entry.path() << "\n"; }
10. Parallel Algorithms
#include <execution> vector<int> v(1000000); // Run sort in parallel sort(execution::par, v.begin(), v.end()); // Parallel for-each for_each(execution::par_unseq, v.begin(), v.end(), [](int& x) { x *= 2; }); // Execution policies: // seq — sequential // par — parallel (uses thread pool) // par_unseq — parallel + vectorized (SIMD)
11. Class Template Argument Deduction (CTAD)
// Before C++17 — had to specify template args pair<int, string> p1(1, "hello"); // C++17 — compiler deduces pair p2(1, "hello"); // pair<int, const char*> vector v{1, 2, 3}; // vector<int> lock_guard lg(myMutex); // lock_guard<mutex>
12. Inline Variables
// Before: only one .cpp could define a global // C++17: inline variable can be defined in headers inline constexpr int MAX_SIZE = 1024;
FeaturePurposeWhen to Use
Structured BindingsUnpack aggregatesLoop over maps, unpack pairs/structs
std::optionalOptional valueFunctions that may fail/find nothing
std::variantType-safe unionHolding one of several types
if constexprCompile-time branchingTemplate meta-programming
string_viewNon-owning string refRead-only string parameters
std::filesystemFile system opsFile/directory manipulation
Parallel AlgorithmsMulti-core STLLarge data processing

C++11 Concurrency Library

std::thread
#include <thread> void task(int id) { cout << "Thread " << id << " running\n"; } thread t1(task, 1); thread t2(task, 2); t1.join(); // wait for t1 to finish t2.join(); // Lambda thread thread t3([]() { cout << "lambda thread\n"; }); t3.join(); // Detach — thread runs independently thread t4(task, 4); t4.detach(); // must NOT join after detach cout << thread::hardware_concurrency(); // logical CPU cores
Mutex & Lock
#include <mutex> mutex mtx; int counter = 0; void increment() { lock_guard<mutex> lock(mtx); // RAII — auto-unlock counter++; } // unique_lock — more flexible (can unlock early, use with condition_variable) unique_lock<mutex> ulock(mtx); counter++; ulock.unlock(); // manual unlock // try_lock — non-blocking if (mtx.try_lock()) { counter++; mtx.unlock(); }
Condition Variable
#include <condition_variable> mutex mtx; condition_variable cv; bool ready = false; int data = 0; // Producer thread producer([&]() { data = 42; ready = true; cv.notify_one(); // wake one waiting thread }); // Consumer thread consumer([&]() { unique_lock<mutex> lock(mtx); cv.wait(lock, [&] { return ready; }); // wait + predicate (no spurious wake) cout << data; // 42 }); producer.join(); consumer.join();
std::future & std::async
#include <future> // async — run function asynchronously, get result via future future<int> result = async(launch::async, []() { // runs in another thread return 42; }); int val = result.get(); // blocks until done, returns 42 // promise — manually set value for a future promise<int> prom; future<int> fut = prom.get_future(); thread t([&prom]() { prom.set_value(100); // fulfill the promise }); cout << fut.get(); // 100 t.join();
Atomic Operations
#include <atomic> atomic<int> counter{0}; // Thread-safe without mutex counter++; // atomic increment counter.fetch_add(5); // atomic add, returns old value counter.load(); // read counter.store(42); // write // Compare-and-swap (CAS) int expected = 10; bool swapped = counter.compare_exchange_strong(expected, 20); // if counter == expected: set to 20 and return true // else: update expected to counter's value and return false
Thread Pool Pattern
// Simple task queue using thread pool concept class ThreadPool { vector<thread> workers; queue<function<void()>> tasks; mutex qMtx; condition_variable cv; bool stop = false; public: ThreadPool(size_t n) { for (size_t i = 0; i < n; i++) workers.emplace_back([this] { while (true) { function<void()> task; { unique_lock<mutex> lock(qMtx); cv.wait(lock, [this] { return stop || !tasks.empty(); }); if (stop && tasks.empty()) return; task = move(tasks.front()); tasks.pop(); } task(); } }); } ~ThreadPool() { { lock_guard<mutex> lock(qMtx); stop = true; } cv.notify_all(); for (auto& w : workers) w.join(); } };

Interview Tip: Know the difference between mutex (exclusive), shared_mutex (readers-writer), atomic (lock-free), and when to use each. atomic is fastest but limited to simple types; mutex is general purpose.