Skip to main content

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

Uniform Initialization (Brace Initialization)

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

1

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.
2

Use move semantics for resource classes

Define a move constructor and move assignment operator (marked noexcept) whenever your class directly manages a resource.
3

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.
4

Replace macros with constexpr

Use constexpr variables for compile-time constants and constexpr functions for compile-time computation instead of #define macros.
5

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.
6

Apply nullptr, not NULL or 0

Always use nullptr for null pointer constants to get type-safe overload resolution and clearer intent.
7

Constrain templates with concepts (C++20)

Use concepts to express template requirements explicitly, improving error messages and documentation.

See Also

Build docs developers (and LLMs) love