Types, Pointers, OOP, STL Containers, Templates, Memory Management, Modern C++ Features — complete C++ reference.
// ── Fundamental Types ──
bool flag = true; // 1 byte
char c = 'A'; // 1 byte
int n = 42; // typically 4 bytes
long l = 100000L; // at least 4 bytes
long long ll = 1e18LL; // at least 8 bytes
float f = 3.14f; // 4 bytes
double d = 3.14159; // 8 bytes
long double ld = 3.14159L; // extended precision
// Auto type deduction (C++11)
auto x = 42; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
auto s = std::string("hello"); // std::string
// constexpr — compile-time evaluation (C++11/14)
constexpr int square(int x) { return x * x; }
constexpr int val = square(5); // computed at compile time
static_assert(val == 25, "oops"); // compile-time check
// nullptr (C++11) — replaces NULL
int* ptr = nullptr;
// Structured bindings (C++17)
auto [key, value] = std::pair{1, "hello"};
auto [a, b, c] = std::tuple{1, 2.0, 'x'};
std::map<std::string, int> m{{"a", 1}, {"b", 2}};
for (auto& [k, v] : m) {
std::cout << k << ": " << v << "\n";
}
// std::optional (C++17)
std::optional<int> find_user(int id) {
if (id > 0) return 42;
return std::nullopt;
}
auto result = find_user(-1);
if (result.has_value()) { /* use *result */ }
auto val = result.value_or(0); // default if empty#include <string>
#include <string_view>
// ── std::string ──
std::string s = "hello";
s += " world"; // append
s.length(); s.size(); s.empty(); // queries
s[0]; s.at(0); // access (at throws)
s.substr(0, 5); // substring
s.find("world"); // search (returns npos if not found)
s.find_first_of("aeiou");
s.replace(0, 5, "hi"); // replace
s.insert(0, "say "); // insert
s.erase(0, 4); // erase
s.c_str(); // const char* for C APIs
std::to_string(42); // int → string
std::stoi("42"); // string → int
std::stod("3.14"); // string → double
// String views (C++17) — non-owning reference
void process(std::string_view sv) { // accepts string, const char*, etc.
std::cout << sv << "\n";
}
std::string_view sv = s; // no copy!
std::string_view sv2 = "literal"; // also works
sv.substr(0, 5); // returns string_view
// String formatting (C++20 std::format)
#include <format>
std::string msg = std::format("Hello, {}! Age: {}", name, 42);
std::string pi = std::format("{:.2f}", 3.14159); // "3.14"
std::string hex = std::format("{:#x}", 255); // "0xff"
// R-strings — raw string literals
auto json = R"({"key": "value", "num": 42})";// ── Range-based for (C++11) ──
std::vector<int> v = {1, 2, 3, 4, 5};
for (const auto& item : v) { } // const reference
for (auto& item : v) { item *= 2; } // mutable reference
for (auto item : v) { } // copy
// ── if with initializer (C++17) ──
if (auto it = m.find("key"); it != m.end()) {
std::cout << it->second << "\n";
}
// ── switch with initializer (C++17) ──
switch (auto status = get_status(); status) {
case Status::OK: break;
case Status::Error: break;
}
// ── Range-based for with structured bindings ──
std::map<std::string, int> scores = {{"alice", 95}, {"bob", 87}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}| Type | Size (typical) | Range (approx) |
|---|---|---|
| int8_t / uint8_t | 1 byte | -128 to 127 / 0 to 255 |
| int16_t / uint16_t | 2 bytes | -32K to 32K |
| int32_t / uint32_t | 4 bytes | -2B to 2B |
| int64_t / uint64_t | 8 bytes | -9.2E18 to 9.2E18 |
| size_t | arch | Unsigned, for sizes |
auto when the type is obvious from context. Use const auto& in range-for loops to avoid copies. Use std::string_view instead of const std::string& for read-only string parameters.// ── Pointers ──
int x = 42;
int* ptr = &x; // ptr stores address of x
*ptr = 10; // dereference — x is now 10
int** ptr2 = &ptr; // pointer to pointer
int*** ptr3 = &ptr2;
// nullptr — modern C++ null pointer
int* p = nullptr;
if (p != nullptr) { *p = 42; }
// Pointer arithmetic
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // points to arr[0]
*(p + 2); // arr[2] = 30
p++; // now points to arr[1]
p - arr; // offset (1)
// ── References ──
int a = 10;
int& ref = a; // reference to a (must initialize!)
ref = 20; // a is now 20
int& bad; // ERROR: reference must be initialized
// References as function parameters (pass by reference)
void swap(int& a, int& b) {
int temp = a; a = b; b = temp;
}
swap(x, y); // modifies actual variables
// const references — pass large objects efficiently
void print(const std::string& s) { // no copy, no modification
std::cout << s << "\n";
}
// ── Pointers vs References ──
// Pointer: can be null, can be reassigned, needs dereferencing
// Reference: cannot be null, cannot be reassigned, no dereference syntax#include <memory>
// ── std::unique_ptr — exclusive ownership ──
auto p1 = std::make_unique<int>(42); // preferred over new
// auto p2 = p1; // ERROR: cannot copy
auto p2 = std::move(p1); // OK: transfer ownership
if (p1 == nullptr) { /* p1 is now empty */ }
// Custom deleter
auto deleter = [](FILE* f) { fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> file(fopen("test.txt", "r"), deleter);
// With arrays
auto arr = std::make_unique<int[]>(10); // arr[0] through arr[9]
arr[3] = 42;
// ── std::shared_ptr — shared ownership ──
auto sp1 = std::make_shared<int>(42);
auto sp2 = sp1; // both point to same data
std::cout << sp1.use_count(); // 2
sp2.reset(); // sp2 no longer owns
std::cout << sp1.use_count(); // 1
// ── std::weak_ptr — non-owning observer ──
std::weak_ptr<int> wp = sp1;
if (auto locked = wp.lock()) { // check if still alive
std::cout << *locked << "\n";
}
// ── Raw pointers (use sparingly) ──
int* raw = new int(42);
delete raw;
raw = nullptr; // always null after delete
int* arr = new int[10];
delete[] arr; // array delete
arr = nullptr;
// ── RAII Pattern ──
class Resource {
FILE* handle;
public:
Resource(const char* path) : handle(fopen(path, "r")) {
if (!handle) throw std::runtime_error("Cannot open file");
}
~Resource() { if (handle) fclose(handle); } // auto cleanup
// No copy (use unique_ptr or make non-copyable)
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
};| Feature | Pointer (*) | Reference (&) |
|---|---|---|
| Can be null | Yes (nullptr) | No |
| Reassignable | Yes | No (always refers to same object) |
| Syntax | *ptr, &x | ref (no special syntax) |
| Initialization | Can defer | Must initialize immediately |
| Common use | Optional ownership, dynamic allocation | Function params, aliases |
unique_ptr — it has zero overhead over raw pointers. Only use shared_ptr when ownership is genuinely shared. Use make_unique/make_shared instead of new — they are exception-safe and more efficient.// ── Classes ──
class Animal {
protected:
std::string name;
int age;
public:
Animal(std::string n, int a) : name(std::move(n)), age(a) {}
virtual ~Animal() = default; // always virtual destructor
virtual void speak() const { // virtual — enables polymorphism
std::cout << name << " makes a sound\n";
}
// Prevent copying
Animal(const Animal&) = delete;
Animal& operator=(const Animal&) = delete;
};
class Dog : public Animal {
std::string breed;
public:
Dog(std::string n, int a, std::string b)
: Animal(std::move(n), a), breed(std::move(b)) {}
// override — compiler checks correctness (C++11)
void speak() const override {
std::cout << name << " says Woof! (" << breed << ")\n";
}
};
// ── Polymorphism ──
void make_speak(const Animal& animal) { // reference to base
animal.speak(); // calls derived speak()!
}
Dog d("Rex", 3, "German Shepherd");
make_speak(d); // "Rex says Woof! (German Shepherd)"
// ── Abstract Class (Interface) ──
class Shape {
public:
virtual double area() const = 0; // pure virtual — must override
virtual double perimeter() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius;
public:
explicit 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 Vec2 {
double x, y;
public:
Vec2(double x = 0, double y = 0) : x(x), y(y) {}
Vec2 operator+(const Vec2& other) const { return {x + other.x, y + other.y}; }
Vec2 operator-(const Vec2& other) const { return {x - other.x, y - other.y}; }
Vec2 operator*(double s) const { return {x * s, y * s}; }
bool operator==(const Vec2& o) const { return x == o.x && y == o.y; }
double operator[](int i) const { return i == 0 ? x : y; }
friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << "(" << v.x << ", " << v.y << ")";
}
};
// ── Templates (class) ──
template<typename T>
class Stack {
std::vector<T> data;
public:
void push(const T& item) { data.push_back(item); }
T pop() { T val = data.back(); data.pop_back(); return val; }
T& top() { return data.back(); }
bool empty() const { return data.empty(); }
size_t size() const { return data.size(); }
};
// ── CRTP — Curiously Recurring Template Pattern ──
template<typename Derived>
class Counter {
static inline int count = 0;
protected:
Counter() { ++count; }
~Counter() { --count; }
public:
static int get_count() { return count; }
};
class MyClass : public Counter<MyClass> { };
// ── Multiple Inheritance ──
class Engine { public: virtual void start() = 0; };
class Electric { public: virtual void charge() = 0; };
class TeslaCar : public Engine, public Electric {
public:
void start() override { std::cout << "Silent start\n"; }
void charge() override { std::cout << "Charging...\n"; }
};| Keyword | Meaning | Use Case |
|---|---|---|
| virtual | Dynamic dispatch | Base class methods for polymorphism |
| override | Check override | Derived class overrides (C++11) |
| final | Prevent override | Lock a virtual method or class |
| = 0 | Pure virtual | Abstract method (no implementation) |
| = default | Default implementation | Use compiler-generated |
| = delete | Disable function | Prevent copying, certain overloads |
| Access | Public | Protected | Private |
|---|---|---|---|
| public inherit | public | protected | private |
| protected inherit | protected | protected | private |
| private inherit | private | private | private |
#include <vector>
#include <deque>
#include <list>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <stack>
#include <queue>
// ── vector — dynamic array ──
std::vector<int> v = {1, 2, 3, 4, 5};
v.push_back(6); // add to end
v.pop_back(); // remove from end
v.emplace_back(7); // construct in-place (faster)
v.insert(v.begin() + 2, 99); // insert at position
v.erase(v.begin() + 1); // erase at position
v.resize(10, 0); // resize to 10, fill with 0
v.reserve(100); // pre-allocate capacity
v.shrink_to_fit(); // free unused memory
v[2]; v.at(2); // access (at throws)
v.front(); v.back(); // first/last
v.data(); // raw pointer to data
v.size(); v.capacity(); v.empty();
// 2D vector
std::vector<std::vector<int>> matrix(3, std::vector<int>(4, 0));
// ── deque — double-ended queue ──
std::deque<int> dq = {2, 3, 4};
dq.push_front(1); // add to front
dq.push_back(5); // add to back
dq.pop_front(); dq.pop_back();
// ── list — doubly-linked list ──
std::list<int> lst = {3, 1, 4, 1, 5};
lst.sort(); lst.reverse();
lst.unique(); // remove consecutive duplicates
lst.remove(1); // remove all 1s
lst.splice(lst.begin(), other_list); // merge lists
// ── map — ordered key-value (red-black tree) ──
std::map<std::string, int> ages = {{"alice", 30}, {"bob", 25}};
ages["charlie"] = 35; // insert/update
ages.insert({"dave", 28}); // insert (won't overwrite)
ages.erase("bob"); // remove
ages.find("alice"); // iterator (end() if not found)
ages.count("alice"); // 0 or 1
ages.contains("alice"); // C++20: bool
for (auto& [key, val] : ages) { } // C++17 structured bindings// ── unordered_map — hash table ──
std::unordered_map<std::string, int> scores;
scores["alice"] = 95;
scores.emplace("bob", 87);
scores.count("alice"); // 0 or 1
scores.bucket_count(); // number of buckets
scores.load_factor(); // current load factor
scores.max_load_factor(0.75); // trigger rehash threshold
// ── set — ordered unique elements ──
std::set<int> s = {5, 3, 1, 4, 2};
s.insert(3); // no duplicate
s.erase(3);
s.find(3); // iterator
s.lower_bound(3); // first >= 3
s.upper_bound(3); // first > 3
s.contains(3); // C++20
// ── unordered_set — hash set ──
std::unordered_set<int> us = {1, 2, 3, 4, 5};
us.contains(3); // C++20
// ── stack — LIFO ──
std::stack<int> stk;
stk.push(1); stk.push(2); stk.push(3);
stk.top(); // 3
stk.pop(); // removes 3
stk.empty(); stk.size();
// ── queue — FIFO ──
std::queue<int> q;
q.push(1); q.push(2); q.push(3);
q.front(); // 1
q.back(); // 3
q.pop(); // removes 1
// ── priority_queue — max-heap ──
std::priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
pq.top(); // 4 (largest)
// Min-heap
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(3); min_pq.push(1); min_pq.push(4);
min_pq.top(); // 1 (smallest)
// ── pair & tuple ──
auto p = std::make_pair(1, "hello");
auto t = std::make_tuple(1, 2.0, "three");
std::get<0>(t); // 1
auto [a, b, c] = t; // C++17| Container | Access | Insert | Find | Notes |
|---|---|---|---|---|
| vector | O(1) | O(1)* | O(n) | Best default choice |
| deque | O(1) | O(1) | O(n) | Fast front/back ops |
| list | O(n) | O(1) | O(n) | Fast insert/erase anywhere |
| map | O(log n) | O(log n) | O(log n) | Sorted keys |
| unordered_map | O(1)* | O(1)* | O(1)* | Hash table |
| set | O(log n) | O(log n) | O(log n) | Sorted unique |
| unordered_set | O(1)* | O(1)* | O(1)* | Hash unique |
// ── Function Templates ──
template<typename T>
T maximum(T a, T b) { return (a > b) ? a : b; }
auto result = maximum(3, 7); // int
auto result2 = maximum(3.14, 2.72); // double
// maximum(3, 3.14); // ERROR: deduced conflicting types
// Multiple type parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { return a + b; }
// C++20: auto parameters (abbreviated function templates)
void print(const auto& item) { std::cout << item << "\n"; }
// ── Template Specialization ──
template<typename T>
struct TypeName { static const char* get() { return "unknown"; } };
template<> struct TypeName<int> { static const char* get() { return "int"; } };
template<> struct TypeName<double> { static const char* get() { return "double"; } };
std::cout << TypeName<int>::get(); // "int"
// ── Variadic Templates (C++11) ──
template<typename... Args>
void print_all(Args... args) {
(std::cout << ... << args) << "\n"; // fold expression (C++17)
}
// Recursive variadic
void print() { } // base case
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // recurse
}
print(1, "hello", 3.14, 'a');
// ── SFINAE & Concepts (C++20) ──
// Old way (SFINAE)
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
T safe_modulo(T a, T b) { return a % b; }
// New way (C++20 Concepts)
#include <concepts>
template<std::integral T>
T safe_modulo(T a, T b) { return a % b; }
// Define your own concept
template<typename T>
concept Printable = requires(T t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};
template<Printable T>
void log(T item) { std::cout << "[LOG] " << item << "\n"; }// ── C++20 Concepts (standard library) ──
#include <concepts>
std::integral<int> // true
std::floating_point<double> // true
std::same_as<int, int> // true
std::derived_from<Dog, Animal> // true
std::convertible_to<int, double> // true
// Constrained template
template<std::integral T>
T factorial(T n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// Requires clause
template<typename T>
requires std::floating_point<T>
T hypotenuse(T a, T b) {
return std::sqrt(a * a + b * b);
}
// ── constexpr templates (C++20) ──
template<typename T>
constexpr T pi = T(3.14159265358979);
constexpr double d = pi<double>;
// ── Type Traits ──
#include <type_traits>
static_assert(std::is_same_v<int, int>);
static_assert(std::is_const_v<const int>);
static_assert(std::is_reference_v<int&>);
static_assert(std::is_array_v<int[5]>);
static_assert(std::is_pointer_v<int*>);
static_assert(std::is_base_of_v<Animal, Dog>);
// Conditional type
using FloatOrDouble = std::conditional_t<sizeof(void*) == 8, double, float>;
// Remove/add const, reference, pointer
using Clean = std::remove_cv_t<const volatile int>; // int
using Ptr = std::add_pointer_t<int>; // int*
using Ref = std::add_lvalue_reference_t<int>; // int&| Type | Syntax | Use Case |
|---|---|---|
| Primary | template<typename T> | General case |
| Full specialization | template<> struct S<int> | Specific type override |
| Partial spec | template<typename T> struct S<T*> | Category override |
// ── Stack vs Heap ──
void example() {
int stack_var = 42; // stack — auto-freed when scope ends
int* heap_var = new int(42); // heap — manual delete required
delete heap_var;
int* arr = new int[100]; // heap array
delete[] arr; // array delete
}
// ── Rule of Three / Five / Zero ──
class Buffer {
char* data;
size_t size;
public:
// Constructor
explicit Buffer(size_t n) : size(n), data(new char[n]) {}
// Destructor
~Buffer() { delete[] data; }
// Copy constructor
Buffer(const Buffer& other) : size(other.size), data(new char[other.size]) {
std::memcpy(data, other.data, size);
}
// Copy assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new char[size];
std::memcpy(data, other.data, size);
}
return *this;
}
// Move constructor (C++11)
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// Move assignment (C++11)
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
// ── Rule of Zero — prefer this! ──
class ModernBuffer {
std::vector<char> data; // manages its own memory
public:
explicit ModernBuffer(size_t n) : data(n) {}
// Compiler generates copy/move/destructor automatically!
};// ── Move Semantics (C++11) ──
// std::move — casts to rvalue reference, enables move
std::string a = "hello";
std::string b = std::move(a); // a is now in "moved-from" state
// a is valid but unspecified — don't use it
// Perfect forwarding
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // preserves value category
}
// ── Smart Pointers for Memory Safety ──
#include <memory>
// unique_ptr — exclusive ownership, zero overhead
auto file = std::make_unique<std::fstream>("test.txt");
// shared_ptr — reference-counted shared ownership
auto shared = std::make_shared<std::string>("hello");
std::cout << shared.use_count() << "\n";
// weak_ptr — break circular references
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // weak to avoid cycle
};
// ── Memory Pool Pattern ──
template<typename T, size_t BlockSize = 1024>
class MemoryPool {
std::vector<std::unique_ptr<T[]>> blocks;
std::vector<T*> free_list;
size_t index = 0;
public:
T* allocate() {
if (!free_list.empty()) {
T* ptr = free_list.back();
free_list.pop_back();
return ptr;
}
if (index == 0 || index >= BlockSize) {
blocks.push_back(std::make_unique<T[]>(BlockSize));
index = 0;
}
return &blocks.back()[index++];
}
void deallocate(T* ptr) { free_list.push_back(ptr); }
};
// ── Placement new ──
char buffer[sizeof(MyClass)];
MyClass* obj = new (buffer) MyClass(args);
obj->~MyClass(); // must call destructor manuallystd::unique_ptr, std::vector, std::string instead of raw new/delete. If you must write a destructor, you almost certainly need the Rule of Five.// ── Lambdas (C++11, improved in 14, 20, 23) ──
auto greet = []() { std::cout << "Hello!\n"; };
auto add = [](int a, int b) { return a + b; };
// Capture by reference
int total = 0;
auto accumulate = [&](int x) { total += x; };
accumulate(5);
accumulate(3);
// Generic lambda (C++14)
auto print_any = [](const auto& x) { std::cout << x << "\n"; };
// Lambda with explicit template (C++20)
auto cast = []<typename T>(T x) -> int { return static_cast<int>(x); };
// Mutable lambda
int counter = 0;
auto increment = [counter]() mutable { return ++counter; };
// Immediately-invoked lambda
auto result = [](int x, int y) { return x + y; }(3, 4);
// std::function — storing lambdas
#include <functional>
std::function<int(int, int)> operation = [](int a, int b) { return a + b; };
operation = std::plus<int>{}; // can reassign
// ── Lambda in STL algorithms ──
std::vector<int> v = {5, 2, 8, 1, 9, 3};
// sort
std::sort(v.begin(), v.end()); // ascending
std::sort(v.begin(), v.end(), std::greater<>{}); // descending
std::sort(v.begin(), v.end(), [](int a, int b) { return a % 3 < b % 3; });
// remove_if + erase (erase-remove idiom)
v.erase(std::remove_if(v.begin(), v.end(), [](int x) { return x < 5; }), v.end());
// count_if, find_if, any_of, all_of, none_of
auto cnt = std::count_if(v.begin(), v.end(), [](int x) { return x > 5; });
auto it = std::find_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
bool any = std::any_of(v.begin(), v.end(), [](int x) { return x > 100; });
// transform
std::transform(v.begin(), v.end(), v.begin(), [](int x) { return x * 2; });
// accumulate
auto sum = std::accumulate(v.begin(), v.end(), 0);// ── C++20 Ranges ──
#include <ranges>
namespace rv = std::ranges::views;
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Chaining range adapters (lazy evaluation!)
auto result = nums
| rv::filter([](int x) { return x % 2 == 0; }) // 2, 4, 6, 8, 10
| rv::transform([](int x) { return x * x; }) // 4, 16, 36, 64, 100
| rv::take(3); // 4, 16, 36
for (int x : result) { std::cout << x << " "; }
// More views
auto even = nums | rv::filter([](int x) { return x % 2 == 0; });
auto squares = nums | rv::transform([](int x) { return x * x; });
auto take3 = nums | rv::take(3);
auto drop5 = nums | rv::drop(5);
auto rev = nums | rv::reverse;
auto uniq = nums | rv::unique; // consecutive duplicates
auto chunk = nums | rv::chunk(3); // groups of 3
auto zip_view = std::views::zip(nums, std::string{"abcdefghij"});
// ── C++20 std::format ──
#include <format>
std::string s1 = std::format("Hello, {}!", "world");
std::string s2 = std::format("Pi = {:.5f}", 3.14159265);
std::string s3 = std::format("Hex: {:#x}", 255);
std::string s4 = std::format("{:>10}", "right"); // right-align width 10
std::string s5 = std::format("{:<10}", "left"); // left-align
std::string s6 = std::format("{:^10}", "center"); // center
// ── C++20 Coroutines ──
#include <coroutine>
Generator<int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
for (int x : range(1, 10)) {
std::cout << x << " ";
}
// ── C++20 std::span ──
#include <span>
void process(std::span<int> data) { // non-owning view
for (auto& x : data) { x *= 2; }
}
int arr[] = {1, 2, 3, 4, 5};
process(arr); // works with C arrays
process(v); // works with vectors
process(std::span{arr, 3}); // subspan| Version | Key Features |
|---|---|
| C++11 | auto, lambda, move, smart ptrs, range-for, threads |
| C++14 | Generic lambda, make_unique, return type deduc, relaxed constexpr |
| C++17 | Structured bindings, optional, variant, if-constexpr, filesystem |
| C++20 | Concepts, ranges, coroutines, format, modules, span |
| C++23 | std::print, std::expected, flat_map, deducing this, std::generator |
-Wall -Wextra -Wpedantic and treat warnings as errors.RAII (Resource Acquisition Is Initialization) ties resource lifetime to object lifetime. Resources (memory, files, sockets, locks) are acquired in constructors and released in destructors. When the object goes out of scope, the destructor runs automatically, guaranteeing cleanup even when exceptions occur. This is why smart pointers, std::fstream, and std::lock_guard work correctly.
new allocates on heap and returns raw pointer — you must manually delete. make_unique creates a unique_ptr in one allocation. make_shared creates a shared_ptr with a single allocation for both the control block and the object (more efficient). Prefer make_* over new — they are exception-safe (no leak if constructor throws between allocation and pointer assignment).
Virtual functions enable runtime polymorphism. Each class with virtual functions has a vtable (array of function pointers). Each object has a vptr (pointer to its class's vtable). When you call a virtual function through a base pointer/reference, the vptr is used to look up the correct function at runtime. This has a small performance cost (indirection). Always mark base class destructors as virtual.
Move semantics transfer ownership of resources instead of copying them. A move constructor/assignment "steals" resources from the source (e.g., copies a pointer, sets source to nullptr). Moves happen automatically with: std::move(), returning local variables by value, function parameters that are temporaries. The compiler also performs copy elision (RVO/NRVO) — in many cases, moves are elided entirely.
Undefined behavior (UB) means the compiler can do anything — your program may crash, produce wrong results, or appear to work. Common examples: null pointer dereference, out-of-bounds array access, use-after-free, double free, integer overflow (signed), uninitialized variable reads, accessing moved-from objects, data races, and modifying an object through multiple non-aliasing pointers.
std::map is implemented as a red-black tree — O(log n) for all operations, keys are always sorted, supports range queries (lower_bound, upper_bound). std::unordered_mapis a hash table — O(1) average for insert/find/erase, no ordering, requires hashable keys. Use map when you need ordering or range queries. Use unordered_map when you need fast lookups and don't care about order.
Concepts are named constraints on template parameters that improve readability and error messages. SFINAE (Substitution Failure Is Not An Error) achieves similar results but with terrible error messages and verbose syntax. Concepts provide: clear intent (std::integral vs std::enable_if_t<std::is_integral_v<T>>), better diagnostics, and can be used with requires clauses.