Documentation Index
Fetch the complete documentation index at: https://mintlify.com/MicrosoftDocs/cpp-docs/llms.txt
Use this file to discover all available pages before exploring further.
Since its creation, C++ has become one of the most widely used programming languages in the world. Well-written C++ programs are fast, efficient, and flexible — capable of working at the highest levels of abstraction and down to the level of the hardware. Modern C++ (C++11 and later) introduced a sweeping set of features that make the language dramatically safer and more expressive without sacrificing performance. The sections below cover the most important modern C++ idioms: RAII with smart pointers, move semantics, perfect forwarding, lambda expressions, range-based for loops, auto and decltype, constexpr, and the C++17/20 vocabulary types std::optional, std::variant, std::span, and concepts.
RAII and Smart Pointers
One of the major classes of bugs in C-style programming is the memory leak: a failure to call delete for memory allocated with new. Modern C++ addresses this through the principle of Resource Acquisition Is Initialization (RAII) — resources are owned by stack objects whose destructors release them automatically.
The C++ Standard Library provides three smart pointer types in <memory>:
unique_ptr
Exclusive ownership. Cannot be copied, only moved. Zero overhead over a raw pointer.
shared_ptr
Shared ownership via reference counting. Multiple owners; resource released when the last owner is destroyed.
weak_ptr
Non-owning observer of a shared_ptr. Does not affect the reference count; used to break circular references.
#include <memory>
class widget {
private:
std::unique_ptr<int[]> data;
public:
widget(const int size) { data = std::make_unique<int[]>(size); }
void do_something() {}
};
void functionUsingWidget() {
widget w(1000000); // lifetime tied to enclosing scope
w.do_something();
} // automatic destruction and deallocation for w and w.data
unique_ptr — Exclusive Ownership
// Create with make_unique (C++14) — never use new directly
auto p = std::make_unique<MyClass>("hello", 42);
p->method(); // access through ->
std::cout << p->value() << "\n";
// Transfer ownership with std::move
auto q = std::move(p); // p is now null; q owns the object
// p.get() == nullptr
// unique_ptr in a factory function
std::unique_ptr<Shape> createShape(ShapeType type) {
switch (type) {
case Circle: return std::make_unique<CircleImpl>(5.0);
case Rectangle: return std::make_unique<RectImpl>(3.0, 4.0);
default: return nullptr;
}
}
shared_ptr — Shared Ownership
auto sp1 = std::make_shared<MyClass>(); // reference count = 1
{
auto sp2 = sp1; // reference count = 2
sp2->method();
} // sp2 destroyed; ref count = 1
// sp1 still valid
// When sp1 goes out of scope, ref count = 0 and object is deleted
// shared_ptr in a container
std::vector<std::shared_ptr<Shape>> shapes;
shapes.push_back(std::make_shared<Circle>(2.0));
shapes.push_back(std::make_shared<Rectangle>(5.0, 3.0));
for (const auto& s : shapes) s->draw();
weak_ptr — Breaking Circular References
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // weak to avoid cycle
int value;
};
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2;
n2->prev = n1; // weak_ptr — n1 is not kept alive by this reference
// Using a weak_ptr
if (auto locked = n2->prev.lock()) { // converts to shared_ptr if still alive
std::cout << locked->value << "\n";
}
Always create smart pointers on a separate line of code, never in a parameter list. Creating them inline in a function argument can cause a subtle resource leak if another argument throws before the smart pointer constructor completes.
Move Semantics
C++11 introduced move semantics to eliminate unnecessary copies when objects are transferred. A move constructor and move assignment operator steal the resources of a temporary (rvalue) object rather than duplicating them:
// Move constructor — takes an rvalue reference &&
MemoryBlock(MemoryBlock&& other) noexcept
: _data(nullptr), _length(0)
{
// Steal the resource
_data = other._data;
_length = other._length;
// Leave source in a valid empty state
other._data = nullptr;
other._length = 0;
}
// Move assignment operator
MemoryBlock& operator=(MemoryBlock&& other) noexcept
{
if (this != &other) {
delete[] _data;
_data = other._data;
_length = other._length;
other._data = nullptr;
other._length = 0;
}
return *this;
}
Use std::move to explicitly cast an lvalue to an rvalue reference, enabling the move constructor:
std::vector<MemoryBlock> v;
v.push_back(MemoryBlock(25)); // move constructor used (temporary)
MemoryBlock a(50);
v.push_back(std::move(a)); // explicit move — a is now empty
Value Types and Move Efficiency
#include <set>
#include <vector>
#include <string>
using namespace std;
set<Widget> LoadHugeData() {
set<Widget> ret;
// ... load data from disk and populate ret
return ret; // NRVO or move — no deep copy
}
// efficient: no deep copy-shuffle
vector<string> v = IfIHadAMillionStrings();
v.insert(begin(v) + v.size()/2, "alice"); // move-based shift
Perfect Forwarding
Perfect forwarding preserves the value category (lvalue or rvalue) of arguments when passing them to another function, using std::forward and a forwarding reference (T&&):
template <typename T, typename... Args>
std::unique_ptr<T> make(Args&&... args) {
// Forward each argument with its original value category preserved
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// Equivalent to make_unique
auto p = make<MyClass>("hello", 42, true);
Lambda Expressions
Lambdas define anonymous function objects inline at the point of use. They are ideal for passing to algorithms and asynchronous operations:
// Lambda syntax: [capture](params) -> return_type { body }
auto multiply = [](double a, double b) -> double {
return a * b;
};
std::cout << multiply(3.0, 4.0); // 12
// Capture by value [=] and by reference [&]
int x = 2, y = 4;
auto inRange = [=](int i) { return i > x && i < y; };
std::vector<int> v{1, 2, 3, 4, 5};
auto it = std::find_if(begin(v), end(v), inRange);
// Named lambda for use with sorting
auto comp = [](const Widget& w1, const Widget& w2) {
return w1.weight() < w2.weight();
};
std::sort(v.begin(), v.end(), comp);
Lambda with mutable
By default, variables captured by value are immutable inside the lambda. Use mutable to allow modification of the captured copies:
int m = 0, n = 0;
auto lam = [&, n](int a) mutable { m = ++n + a; };
lam(4);
// m == 5, n is still 0 (captured by value, modified inside lambda)
constexpr Lambdas (C++17)
int y = 32;
auto answer = [y]() constexpr {
int x = 10;
return y + x;
};
constexpr int result = answer(); // evaluated at compile time
Generic Lambdas (C++14)
auto add = [](auto first, auto second) {
return first + second;
};
add(1, 2); // int + int = 3
add(1.5, 2.5); // double + double = 4.0
add(std::string{"hello"}, std::string{" world"});
Range-Based for Loops
Range-based for eliminates error-prone index arithmetic and works with any type that provides begin() and end():
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v{1, 2, 3};
// C-style (avoid)
for (int i = 0; i < (int)v.size(); ++i)
std::cout << v[i];
// Modern C++ — preferred
for (auto& num : v)
std::cout << num;
// const reference for read-only traversal
for (const auto& num : v)
std::cout << num;
// Raw arrays also work
int x[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (auto y : x)
std::cout << y << " ";
}
auto and Type Deduction
auto reduces verbosity and improves code robustness when types are renamed or changed:
// Iterator types — auto makes this readable
map<int, list<string>>::iterator it = m.begin(); // C-style
auto it = m.begin(); // modern C++
// auto preserves exact type from complex expressions
auto result = std::make_tuple(1, 3.14, "hello");
// Structured bindings (C++17) with auto
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}
constexpr — Compile-Time Computation
constexpr variables and functions are evaluated at compile time, replacing macro-defined constants and enabling computation in template arguments:
// constexpr replaces #define for constants
#define SIZE 10 // C-style (avoid)
constexpr int size = 10; // modern C++
// constexpr function evaluated at compile time if args are constant
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int f5 = factorial(5); // computed at compile time: 120
// constexpr in template arguments
std::array<int, factorial(4)> arr; // std::array<int, 24>
nullptr and Null Pointer Safety
The keyword nullptr is a type-safe null pointer constant that replaces the macro NULL and the integer literal 0:
void process(int* ptr) { /* handles pointer */ }
void process(int val) { /* handles value */ }
process(NULL); // Ambiguous! Could call either overload
process(nullptr); // Unambiguous: calls process(int*)
std::unique_ptr<int> p = nullptr; // clearly null
if (p != nullptr) p->doWork();
C++17 Vocabulary Types
std::optional — Nullable Values Without Pointers
#include <optional>
std::optional<double> safeDivide(double a, double b) {
if (b == 0.0) return std::nullopt;
return a / b;
}
auto result = safeDivide(10.0, 3.0);
if (result) {
std::cout << "Result: " << *result << "\n";
} else {
std::cout << "Division by zero\n";
}
// value_or provides a default
double val = safeDivide(10.0, 0.0).value_or(0.0);
std::variant — Type-Safe Union
std::variant is a type-safe alternative to union. It holds one of a fixed set of types and knows which one is active:
#include <variant>
#include <string>
using Config = std::variant<int, double, std::string, bool>;
Config cfg = 42;
cfg = "hello";
cfg = 3.14;
// Access using std::visit
std::visit([](auto&& val) {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, std::string>)
std::cout << "string: " << val << "\n";
else
std::cout << "other: " << val << "\n";
}, cfg);
// Type-safe access
if (auto* s = std::get_if<std::string>(&cfg)) {
std::cout << "Got string: " << *s << "\n";
}
std::string_view — Non-Owning String Reference
#include <string_view>
// No copy — just a view into existing string data
void processName(std::string_view name) {
std::cout << "Hello, " << name << "!\n";
}
processName("Alice"); // string literal
processName(std::string{"Bob"}); // std::string
processName(std::string_view{"Carol", 5}); // substring
std::span — Non-Owning Array View (C++20)
std::span is a lightweight, non-owning reference to a contiguous sequence of elements — safer than passing raw pointer + length:
#include <span>
#include <vector>
void processData(std::span<const int> data) {
for (int val : data)
std::cout << val << " ";
}
std::vector<int> v{1, 2, 3, 4, 5};
int arr[] = {6, 7, 8, 9, 10};
processData(v); // works with vector
processData(arr); // works with raw array
processData({v.data(), 3}); // first 3 elements only
C++11 unified object initialization syntax with braces {}, which prevents narrowing conversions and works for all types:
#include <vector>
struct S {
std::string name;
float num;
S(std::string s, float f) : name(s), num(f) {}
};
// C-style initialization
std::vector<S> v;
S s1("Norah", 2.7f);
v.push_back(s1);
// Modern C++: in-place brace initialization
std::vector<S> v2{s1};
std::vector<S> v3{ {"Norah", 2.7f}, {"Frank", 3.5f}, {"Jeri", 85.9f} };
// Prevents narrowing:
// int i{3.14}; // Error: narrowing conversion from double to int
Structured Bindings (C++17)
Structured bindings let you unpack pairs, tuples, structs, and maps into named variables:
#include <map>
#include <tuple>
// Unpack pair
auto pair = std::make_pair(42, "hello");
auto [num, str] = pair;
// Unpack tuple
auto tpl = std::make_tuple(1, 2.5, "world");
auto [a, b, c] = tpl;
// Unpack map entries
std::map<std::string, int> scores{{"Alice", 95}, {"Bob", 87}};
for (auto& [name, score] : scores) {
std::cout << name << " = " << score << "\n";
}
// Unpack struct
struct Point { int x, y; };
Point p{10, 20};
auto [px, py] = p;
Concepts (C++20)
Concepts constrain template parameters with human-readable requirements, producing clear error messages when constraints are not satisfied:
#include <concepts>
template <std::integral T>
T gcd(T a, T b) {
return b == 0 ? a : gcd(b, a % b);
}
// Custom concept
template <typename T>
concept Printable = requires(T t, std::ostream& os) {
{ os << t } -> std::same_as<std::ostream&>;
};
template <Printable T>
void print(const T& val) {
std::cout << val << "\n";
}
print(42); // OK
print(3.14); // OK
// print(MyClass{}); // Error if MyClass has no operator<<
Quick Reference: Modern C++ Feature Checklist
Replace raw pointers with smart pointers
Use std::unique_ptr for single ownership and std::shared_ptr for shared ownership. Never call new/delete directly in application code.
Use move semantics for resource classes
Define a move constructor and move assignment operator (marked noexcept) whenever your class directly manages a resource.
Prefer auto and range-based for
Use auto to avoid verbose type names and prevent implicit conversions. Use range-based for loops instead of index-based loops.
Replace macros with constexpr
Use constexpr variables for compile-time constants and constexpr functions for compile-time computation instead of #define macros.
Use std::optional and std::variant
Use std::optional for nullable values without pointer overhead, and std::variant as a type-safe alternative to union.
Apply nullptr, not NULL or 0
Always use nullptr for null pointer constants to get type-safe overload resolution and clearer intent.
Constrain templates with concepts (C++20)
Use concepts to express template requirements explicitly, improving error messages and documentation.
See Also