What Is Modern C++?
Modern C++ refers to the style and features introduced since C++11 and expanded in C++14, C++17, C++20, and C++23. It focuses on safety, expressiveness, performance, and clean design.
Smart Pointers
Smart pointers automatically manage dynamically allocated memory.
They free memory when the object is no longer needed, preventing memory leaks and eliminating manual delete.
- std::unique_ptr — single owner, fastest and safest default choice.
- std::shared_ptr — multiple owners using reference counting.
- std::weak_ptr — non‑owning reference used with shared_ptr to avoid cycles.
Prefer std::unique_ptr unless shared ownership is required.
#include <memory>
#include <iostream>
int main() {
std::unique_ptr ptr = std::make_unique(42);
std::cout << *ptr << std::endl;
} // ptr automatically deletes the memory here
The memory allocated by make_unique is automatically released when ptr goes out of scope.
Move Semantics
Move semantics let you transfer resources instead of copying them — essential for performance.
std::vector a = {1,2,3};
std::vector b = std::move(a); // moves instead of copies
Lambdas
A lambda is an anonymous inline function. They are commonly used with STL algorithms, callbacks, and short operations.
General syntax: [capture](parameters) -> return_type { body }
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// 1. Simple lambda
auto hello = []() {
std::cout << "Hello\n";
};
hello();
// 2. Lambda with parameters
auto add = [](int a, int b) {
return a + b;
};
std::cout << add(2,3) << std::endl;
// 3. Capture by value
int x = 10;
auto show = [x]() {
std::cout << x << std::endl;
};
// 4. Capture by reference
auto modify = [&x]() {
x++;
};
modify();
// 5. Capture everything
auto captureAll = [=]() { std::cout << x; }; // by value
auto captureRef = [&]() { x++; }; // by reference
// 6. Lambda with explicit return type
auto divide = [](double a, double b) -> double {
return a / b;
};
// 7. Lambda used with STL
std::vector v = {3,1,4};
std::sort(v.begin(), v.end(), [](int a, int b) {
return a < b;
});
}
Lambdas are widely used with STL algorithms like sort,
for_each, and find_if.
Type Deduction (auto)
auto lets the compiler automatically determine a variable’s type
from its initializer. It reduces verbosity and is commonly used with
STL containers and iterators.
The type of auto is deduced at compile time from the assigned value.
#include <vector>
#include <iostream>
int main() {
// 1. Basic type deduction
auto x = 10; // int
auto y = 3.14; // double
auto name = "C++"; // const char*
// 2. With references
int a = 5;
auto b = a; // copy (int)
auto& c = a; // reference to a
// 3. With const
const int k = 7;
auto m = k; // int (const removed)
const auto n = k; // const int
// 4. Iterators (very common use)
std::vector v = {1,2,3};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << std::endl;
}
// 5. Range-based loop
for (auto value : v) {
std::cout << value << std::endl;
}
}
auto is especially useful when working with complex STL types
like iterators and template return values.
optional, variant, any
Modern C++ provides safer data containers for representing absent values and type-safe unions.
std::optional find_value(bool ok) {
if (ok) return 42;
return std::nullopt;
}
Ranges (C++20)
The ranges library allows chaining operations like
filter, transform, and take
into readable pipelines.
#include <vector>
#include <ranges>
std::vector vec = {1,2,3,4,5,6};
auto even = vec | std::views::filter([](int x){ return x % 2 == 0; });
The pipeline keeps only even numbers from the vector.
Threads (Multithreading)
A thread allows a program to run multiple tasks at the same time.
C++ provides the <thread> library to create and manage threads.
Each thread runs a function independently from the main program.
#include <iostream>
#include <thread>
void worker() {
std::cout << "Hello from thread\n";
}
int main() {
std::thread t(worker); // start new thread
t.join(); // wait for the thread to finish
}
join() blocks the main thread until the worker thread finishes.
constexpr
constexpr tells the compiler that a value or function can be
evaluated at compile time. This can improve performance and enable
constant expressions in places like array sizes and templates.
If all inputs are known at compile time, the result is computed during compilation.
#include <iostream>
constexpr int square(int x) {
return x * x;
}
int main() {
constexpr int a = square(5); // computed at compile time
std::cout << a << std::endl;
}
Functions marked constexpr can run at compile time when given constant inputs.
Templates
Templates enable writing generic code that works with any data type. Modern C++ has significantly enhanced their power and usability.
- Variadic Templates: Functions/classes with any number of arguments.
- Alias Templates: Create type aliases for complex template types.
- Fold Expressions (C++17): Apply binary operations across variadic packs.
- Non-Type Template Parameters: Use types like `auto` for more flexibility.
#include <iostream>
#include <string>
// Example: Variadic Template with Fold Expression (C++17)
template
void print(Args... args) {
// (std::cout << ... << args) is a fold expression
// It expands to: std::cout << arg1 << arg2 << ...
(std::cout << ... << args) << std::endl;
}
int main() {
print("Hello", " ", 123, " ", 3.14);
print("Only one argument");
}
Templates are crucial for building highly generic and reusable C++ libraries.
Strongly Typed Enums
enum class creates a strongly typed enumeration.
Unlike traditional enum, it prevents implicit conversions
and keeps enumerator names scoped.
This improves type safety and avoids name conflicts in larger programs.
#include <iostream>
enum class Color { Red, Green, Blue };
int main() {
Color c = Color::Red;
if (c == Color::Red) {
std::cout << "Red selected\n";
}
}
Enumerator names must be accessed with the scope operator:
Color::Red, Color::Green, etc.