Перейти к основному содержимому

5.06. Справочник по C++

Разработчику Архитектору

Справочник по C++

Общие сведения

C++ — это компилируемый, статически типизированный, мультипарадигмальный язык программирования, поддерживающий процедурное, объектно-ориентированное, обобщённое и функциональное программирование. Он предоставляет низкоуровневый доступ к памяти, управление ресурсами через RAII (Resource Acquisition Is Initialization), и мощную систему шаблонов.

Язык стандартизирован комитетом ISO/IEC JTC1/SC22/WG21. Основные версии стандарта:

  • C++98 — первая официальная версия
  • C++03 — технические исправления
  • C++11 — крупное обновление с поддержкой современных практик
  • C++14 — уточнения и расширения
  • C++17 — улучшения стандартной библиотеки и языковых возможностей
  • C++20 — модули, концепции, корутины, диапазоны
  • C++23 — текущий актуальный стандарт на 2026 год

Компиляторы: GCC, Clang, MSVC, Intel C++ Compiler.


Базовые типы данных

Целочисленные типы

ТипРазмер (обычно)Диапазон значенийЗнаковый
bool1 байтfalse, trueбеззнаковый
char1 байт-128…127 или 0…255зависит от реализации
signed char1 байт-128…127знаковый
unsigned char1 байт0…255беззнаковый
short2 байта-32 768…32 767знаковый
unsigned short2 байта0…65 535беззнаковый
int4 байта-2 147 483 648…2 147 483 647знаковый
unsigned int4 байта0…4 294 967 295беззнаковый
long4 или 8 байтзависит от платформызнаковый
unsigned long4 или 8 байтзависит от платформыбеззнаковый
long long8 байт-9 223 372 036 854 775 808…9 223 372 036 854 775 807знаковый
unsigned long long8 байт0…18 446 744 073 709 551 615беззнаковый

Типы с фиксированным размером (из <cstdint>):

  • int8_t, int16_t, int32_t, int64_t
  • uint8_t, uint16_t, uint32_t, uint64_t
  • intptr_t, uintptr_t — для хранения указателей как целых чисел

Вещественные типы

ТипРазмерПример точностиПример диапазона
float4 байта~7 десятичных цифр±1.2×10⁻³⁸ … ±3.4×10³⁸
double8 байт~15–17 цифр±2.2×10⁻³⁰⁸ … ±1.8×10³⁰⁸
long double10–16 байтзависит от реализациирасширенный диапазон

Специальные значения для вещественных типов

  • INFINITY — положительная бесконечность
  • -INFINITY — отрицательная бесконечность
  • NaN — «Not a Number», результат недопустимых операций (например, 0.0 / 0.0)
  • Доступ через <cmath> или <limits>

Типы указателей и ссылок

  • Указатель: T* — хранит адрес объекта типа T
  • Ссылка: T& — псевдоним существующего объекта
  • Rvalue-ссылка: T&& — используется для перемещения и perfect forwarding

Пустой тип

  • void — отсутствие типа; используется в сигнатурах функций, не возвращающих значение, или в указателях (void*)

Типы из стандартной библиотеки

  • std::size_t — беззнаковый тип для размеров и индексов
  • std::ptrdiff_t — знаковый тип для разности указателей
  • std::nullptr_t — тип литерала nullptr

Литералы

Целочисленные литералы

  • Десятичные: 42, 0
  • Восьмеричные: 042 (начинаются с 0)
  • Шестнадцатеричные: 0x2A, 0XFF
  • Двоичные (C++14): 0b101010

Суффиксы:

  • u или U — беззнаковый (42u)
  • l или Llong (42L)
  • ll или LLlong long (42LL)
  • Комбинации: 42ULL, 123uLL

Вещественные литералы

  • Десятичные: 3.14, .5, 1.
  • Экспоненциальная форма: 1.23e-4, 6.022e23f
  • Суффиксы:
    • f или Ffloat (3.14f)
    • l или Llong double (3.14L)
    • Без суффикса — double

Символьные и строковые литералы

  • Символ: 'a', '\n', '\x41', '\u0041', '\U00000041'
  • Многосимвольный литерал: 'ABCD' (реализация-зависимый int)
  • Строки:
    • "Hello"const char[N]
    • u8"Привет" — UTF-8 строка (const char[N])
    • u"юникод" — UTF-16 (const char16_t[N])
    • U"Unicode" — UTF-32 (const char32_t[N])
    • L"wide" — широкая строка (const wchar_t[N])

C++11 и выше:

  • Сырые строки: R"(C:\Path\To\File)" — символы \ не экранируются
  • Пользовательские литералы: 42_km, "hello"_s (определяются через operator"")

Логические литералы

  • true, false — значения типа bool

Указательный литерал

  • nullptr — нулевой указатель (тип std::nullptr_t)

Операторы

Арифметические

  • + — унарный плюс, сложение
  • - — унарный минус, вычитание
  • * — умножение
  • / — деление
  • % — остаток от деления (только для целых типов)

Побитовые

  • ~ — побитовое НЕ
  • & — побитовое И
  • | — побитовое ИЛИ
  • ^ — побитовое исключающее ИЛИ
  • << — сдвиг влево
  • >> — сдвиг вправо (арифметический для знаковых, логический для беззнаковых)

Логические

  • ! — логическое НЕ
  • && — логическое И (с коротким замыканием)
  • || — логическое ИЛИ (с коротким замыканием)

Операторы сравнения

  • == — равно
  • != — не равно
  • < — меньше
  • <= — меньше или равно
  • > — больше
  • >= — больше или равно
  • <=> — оператор трехстороннего сравнения (C++20, "spaceship operator")

Операторы присваивания

  • = — простое присваивание
  • +=, -=, *=, /=, %=
  • &=, |=, ^=
  • <<=, >>=

Инкремент и декремент

  • ++x — префиксный инкремент (возвращает новое значение)
  • x++ — постфиксный инкремент (возвращает старое значение)
  • --x, x-- — аналогично для декремента

Условный оператор

  • condition ? expr1 : expr2 — тернарный условный оператор

Операторы работы с памятью

  • * — разыменование указателя
  • & — взятие адреса
  • new — динамическое выделение памяти
  • delete — освобождение памяти (для new)
  • new[], delete[] — для массивов

Операторы доступа

  • . — доступ к члену объекта
  • -> — доступ к члену через указатель
  • :: — разрешение области видимости (scope resolution)
  • [] — индексация массива или контейнера

Прочие операторы

  • , — оператор запятая (выполняет левое выражение, возвращает правое)
  • sizeof — размер в байтах (sizeof(int), sizeof x)
  • sizeof... — размер параметров шаблона-пакета (C++11)
  • alignof — выравнивание типа
  • noexcept — проверка, может ли выражение выбросить исключение
  • typeid — информация о типе во время выполнения (RTTI)
  • dynamic_cast, static_cast, reinterpret_cast, const_cast — явные приведения типов

Управляющие конструкции

Условные операторы

if / else if / else

if (условие) {
// выполняется, если условие истинно
} else if (другое_условие) {
// выполняется, если первое ложно, а второе истинно
} else {
// выполняется, если все условия ложны
}

C++17 позволяет объявлять переменную прямо в условии:

if (int x = getValue(); x > 0) {
// x доступен только внутри этого блока
}

switch

Работает с целочисленными типами, перечислениями и char.

switch (выражение) {
case значение1:
// код
break;
case значение2:
// код
break;
default:
// выполняется, если ни один case не совпал
}

Особенности:

  • Поддерживает «проваливание» (fallthrough) — выполнение нескольких case подряд без break
  • C++17: атрибут [[fallthrough]] явно указывает намеренное проваливание
  • C++14: switch может использовать инициализацию в условии (аналогично if)

Циклы

while

while (условие) {
// тело цикла
}

Проверка условия — перед каждой итерацией.

do-while

do {
// тело цикла
} while (условие);

Тело выполняется хотя бы один раз.

for (традиционный)

for (инициализация; условие; инкремент) {
// тело
}

Пример:

for (int i = 0; i < 10; ++i) { /* ... */ }

for (диапазонный, range-based, C++11)

for (элемент : контейнер) {
// обработка элемента
}

Можно использовать ссылки для модификации:

for (auto& x : vec) x *= 2;

Поддерживается любым объектом, имеющим begin() и end() (включая массивы).

Управление циклом

  • break — немедленный выход из цикла
  • continue — переход к следующей итерации

Функции

Объявление и определение

возвращаемый_тип имя(параметры) {
// тело
}

Пример:

int add(int a, int b) {
return a + b;
}

Параметры

  • По значению: копия аргумента (int x)
  • По ссылке: псевдоним (int& x)
  • По константной ссылке: безопасное чтение без копирования (const int& x)
  • По указателю: явная передача адреса (int* x)

Возврат значения

  • return выражение; — возвращает результат
  • Для void-функций return; завершает выполнение

Перегрузка функций

Функции с одинаковым именем, но разными параметрами:

void print(int x);
void print(double x);
void print(const std::string& s);

Компилятор выбирает подходящую версию по типам аргументов.

Аргументы по умолчанию

Указываются в объявлении:

void greet(std::string name, std::string greeting = "Hello");

Вызов: greet("Alice") → использует "Hello" как второй аргумент.

Правило: аргументы по умолчанию должны идти справа налево — нельзя пропустить средний аргумент.

Встроенные функции (inline)

Подсказка компилятору заменить вызов телом функции:

inline int square(int x) { return x * x; }

Современные компиляторы игнорируют inline как оптимизационную подсказку, но он обязателен для определения функций в заголовочных файлах без нарушения ODR (One Definition Rule).

Лямбда-выражения (C++11)

Анонимные функции, определяемые на месте:

auto f = [](int x, int y) { return x + y; };
int result = f(3, 4); // 7

Захват переменных:

  • [=] — захват всех внешних переменных по значению
  • [&] — захват по ссылке
  • [x, &y] — явный захват: x по значению, y по ссылке
  • [this] — захват указателя на текущий объект

Лямбды могут быть:

  • mutable — позволяют изменять копии захваченных значений
  • иметь спецификаторы (constexpr, noexcept)
  • преобразовываться в указатель на функцию (если не захватывают ничего)

Пример:

int offset = 10;
auto add_offset = [=](int x) { return x + offset; };

Классы и структуры

Объявление

class Имя {
// по умолчанию private
};

struct Имя {
// по умолчанию public
};

Различие между class и struct — только в уровне доступа по умолчанию.

Члены класса

  • Поля — переменные внутри класса
  • Методы — функции-члены
  • Статические члены — принадлежат классу, а не экземпляру
  • Дружественные функции/классы — имеют доступ к приватным членам

Уровни доступа

  • public — доступен извне
  • private — доступен только внутри класса
  • protected — доступен внутри класса и его наследников

Пример класса

class Rectangle {
private:
double width, height;

public:
Rectangle(double w, double h) : width(w), height(h) {}

double area() const {
return width * height;
}

void scale(double factor) {
width *= factor;
height *= factor;
}
};

Конструкторы, деструкторы и специальные функции-члены

Конструкторы

  • Конструктор по умолчанию: без параметров или со всеми параметрами по умолчанию
  • Параметризованный конструктор: принимает аргументы
  • Конструктор копирования: ClassName(const ClassName&)
  • Конструктор перемещения (C++11): ClassName(ClassName&&)
  • Делегирующие конструкторы (C++11): один конструктор вызывает другой

Инициализация полей через список инициализаторов:

ClassName(int x) : member(x) {}

Деструктор

Вызывается при уничтожении объекта:

~ClassName() {
// освобождение ресурсов
}

Деструктор автоматически вызывается для локальных объектов при выходе из области видимости и для динамических — при delete.

Правило пяти (Rule of Five)

Если определён хотя бы один из следующих, стоит определить все:

  • Деструктор
  • Конструктор копирования
  • Оператор присваивания копированием (operator=(const T&))
  • Конструктор перемещения
  • Оператор присваивания перемещением (operator=(T&&))

C++11 и выше: можно явно запросить поведение по умолчанию:

ClassName(const ClassName&) = default;
ClassName& operator=(const ClassName&) = default;

Или запретить:

ClassName(const ClassName&) = delete;

const-методы

Метод, помеченный const, гарантирует, что не изменит состояние объекта:

double getValue() const { return value; }

Такой метод может вызываться на const-объектах.

static-методы и поля

  • static-поле: одно на весь класс, объявляется в классе, определяется вне его
  • static-метод: не имеет доступа к this, работает только со статическими данными

Операторы

Перегрузка операторов возможна как внутри класса (operator@), так и вне (operator@(T, U)).

Часто перегружаемые:

  • +, -, *, /
  • ==, !=, <=>
  • [], (), ->, *
  • <<, >> (для потоков)
  • new, delete (редко)

Пример:

Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}

Шаблоны

Шаблоны — механизм обобщённого программирования, позволяющий писать код, независимый от конкретных типов.

Шаблоны функций

template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}

Использование:

int x = max(3, 5);          // T = int
double y = max(2.7, 1.3); // T = double

Альтернативное ключевое слово: class вместо typename — семантически эквивалентно:

template <class T> ...

Шаблоны классов

template <typename T>
class Stack {
private:
std::vector<T> data;
public:
void push(const T& item) { data.push_back(item); }
T pop() {
T top = data.back();
data.pop_back();
return top;
}
bool empty() const { return data.empty(); }
};

Использование:

Stack<int> intStack;
Stack<std::string> stringStack;

Не типовые параметры шаблонов

Параметры могут быть значениями, а не только типами:

template <int N>
class Array {
int data[N];
public:
int& operator[](int i) { return data[i]; }
};

Использование: Array<10> arr;

Параметры по умолчанию в шаблонах

template <typename T, typename Allocator = std::allocator<T>>
class MyVector { /* ... */ };

Специализация шаблонов

Полная специализация:

template <>
class Stack<bool> {
// особая реализация для bool
};

Частичная специализация (только для классов):

template <typename T>
class Stack<T*> {
// реализация для указателей любого типа
};

Автоматическое выведение аргументов шаблона (C++17)

std::pair p(1, 2.5);       // p имеет тип std::pair<int, double>
std::vector v{1, 2, 3}; // v — std::vector<int>

Концепции (Concepts, C++20)

Ограничения на типы в шаблонах:

template <typename T>
concept Integral = std::is_integral_v<T>;

template <Integral T>
T add(T a, T b) {
return a + b;
}

Или с сокращённым синтаксисом:

Integral auto add(Integral auto a, Integral auto b) {
return a + b;
}

Исключения

Механизм обработки ошибок во время выполнения.

Базовый синтаксис

try {
// код, который может выбросить исключение
if (error) throw std::runtime_error("Ошибка!");
} catch (const std::exception& e) {
// обработка
std::cerr << "Исключение: " << e.what() << '\n';
} catch (...) {
// обработка любого другого исключения
}

Типы исключений

  • std::exception — базовый класс
  • std::runtime_error, std::logic_error — общие категории
  • std::invalid_argument, std::out_of_range, std::bad_alloc — конкретные ошибки

Спецификаторы исключений

  • noexcept — функция не выбрасывает исключений:
    void safe_function() noexcept { /* ... */ }
  • Вызов throw() устарел с C++11, заменён на noexcept.

RAII и исключения

Ресурсы (память, файлы, соединения) должны управляться через объекты с деструкторами (например, std::unique_ptr, std::ifstream), чтобы обеспечить корректную очистку даже при выбросе исключения.


Препроцессор и этапы компиляции

Этапы обработки исходного кода

  1. Лексический анализ и препроцессинг — обработка директив #
  2. Компиляция в объектный код — генерация .o или .obj
  3. Компоновка (линковка) — объединение объектных файлов и библиотек в исполняемый файл

Основные директивы препроцессора

  • #include <file> — вставка содержимого заголовочного файла
  • #include "file" — поиск сначала в текущей директории
  • #define NAME value — макроопределение
  • #define MACRO(params) replacement — макрос с параметрами
  • #undef NAME — удаление определения
  • #if, #ifdef, #ifndef, #else, #elif, #endif — условная компиляция
  • #pragma — зависящие от компилятора инструкции (#pragma once)
  • #error "message" — остановка компиляции с сообщением

Рекомендации

  • Избегать макросов там, где возможны константы, constexpr, inline или шаблоны
  • Использовать include guards или #pragma once в заголовках
  • Не использовать макросы для имитации функций — они не проверяют типы и нарушают правила области видимости

Пространства имён (Namespaces)

Группировка связанных объявлений для избежания коллизий имён.

Объявление

namespace Graphics {
class Window { /* ... */ };
void drawLine(int x1, int y1, int x2, int y2);
}

Использование

  • Полное имя: Graphics::Window w;
  • Директива using namespace:
    using namespace Graphics;
    Window w; // теперь без префикса
  • Объявление using для одного имени:
    using Graphics::Window;

Вложенные пространства

namespace Engine {
namespace Rendering {
class Renderer { /* ... */ };
}
}
// Или (C++17):
namespace Engine::Rendering {
class Renderer { /* ... */ };
}

Анонимные пространства

Заменяют static на уровне файла:

namespace {
int helperFunction() { return 42; } // видна только в этом файле
}

Типы: перечисления, объединения, псевдонимы

Перечисления (enum)

Обычные перечисления (C++98):

enum Color { Red, Green, Blue };
  • Значения неявно конвертируются в целые
  • Имена попадают в окружающую область видимости

Перечисления с областью видимости (scoped enums, C++11):

enum class Status { Active, Inactive, Pending };
  • Нет неявного преобразования в целые
  • Имена доступны только через Status::Active
  • Можно указать базовый тип:
    enum class Code : uint8_t { OK = 0, ERR = 1 };

Объединения (union)

Хранят только одно значение из списка в один момент времени:

union Data {
int i;
float f;
char str[20];
};

Особенности:

  • Размер равен размеру самого большого поля
  • Начиная с C++11, можно определять конструкторы и методы, если union не содержит несовместимых типов (например, с нетривиальными деструкторами)
  • Безопасное использование требует внешнего тега (tagged union) или std::variant (C++17)

Псевдонимы типов

typedef (устаревший стиль):

typedef unsigned long ulong;
typedef int (*FuncPtr)(int, int);

using (современный стиль, C++11):

using ulong = unsigned long;
using FuncPtr = int(*)(int, int);
using StringMap = std::map<std::string, int>;

Преимущества using:

  • Читаемость
  • Поддержка шаблонных псевдонимов:
    template <typename T>
    using Vec = std::vector<T, MyAllocator<T>>;

Указатели и ссылки

Указатели

Указатель — переменная, хранящая адрес другого объекта в памяти.

int x = 42;
int* p = &x; // p содержит адрес x
int y = *p; // разыменование: y = 42

Особенности:

  • Указатель может быть nullptr — нулевой указатель
  • Указатель может быть переназначен на другой адрес
  • Арифметика указателей допустима для массивов: p + 1, p - q
  • Указатель на void (void*) — универсальный указатель, не может быть разыменован без приведения типа

Типы указателей:

  • Указатель на функцию: int (*func)(int, int)
  • Указатель на член класса: int MyClass::* ptr = &MyClass::value
  • Указатель на метод: void (MyClass::*method)() = &MyClass::doSomething

Ссылки

Ссылка — псевдоним существующего объекта.

int x = 10;
int& r = x; // r — это то же самое, что и x
r = 20; // x теперь равно 20

Особенности:

  • Ссылка должна быть инициализирована при объявлении
  • Ссылка не может быть переназначена
  • Ссылка не может быть нулевой
  • Ссылка занимает столько же места, сколько и сам объект (на уровне реализации — обычно передаётся как указатель, но с семантикой псевдонима)

Rvalue-ссылки (C++11)

Используются для реализации семантики перемещения и perfect forwarding.

int&& rref = 42;            // привязка к временному значению
void func(int&& x); // принимает rvalue

Перемещение:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // v1 "опустошается"

Правило трёх/пяти и ссылки

Если класс управляет ресурсами (например, динамической памятью), необходимо явно определить:

  • Деструктор
  • Конструктор копирования
  • Оператор присваивания копированием
  • (C++11) Конструктор перемещения
  • (C++11) Оператор присваивания перемещением

Умные указатели

Умные указатели — RAII-обёртки над сырыми указателями, автоматически управляющие временем жизни объекта.

std::unique_ptr

  • Единственный владелец объекта
  • Нельзя копировать, можно только перемещать
  • Автоматически вызывает delete при выходе из области видимости
#include <memory>
auto ptr = std::make_unique<int>(42);
// или: std::unique_ptr<int> ptr(new int(42));

Пользовательский удалитель:

auto ptr = std::unique_ptr<FILE, decltype(&fclose)>(
fopen("file.txt", "r"), &fclose
);

std::shared_ptr

  • Совместное владение через счётчик ссылок
  • Объект удаляется, когда последний shared_ptr выходит из области видимости
  • Накладные расходы: счётчик ссылок в куче, атомарные операции при копировании
auto ptr1 = std::make_shared<int>(100);
auto ptr2 = ptr1; // счётчик = 2

std::weak_ptr

  • Наблюдатель за объектом, управляемым shared_ptr
  • Не увеличивает счётчик ссылок
  • Используется для разрыва циклических зависимостей
std::weak_ptr<int> wptr = ptr1;
if (auto sptr = wptr.lock()) {
// объект ещё жив, sptr — shared_ptr на него
}

Рекомендации

  • Предпочитать std::make_unique и std::make_shared прямому new
  • Избегать сырых new/delete вне низкоуровневых систем
  • Не хранить умные указатели в контейнерах как void*
  • Не использовать shared_ptr без необходимости — он дороже unique_ptr

Массивы и строки

C-массивы

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // массив неявно преобразуется в указатель на первый элемент

Недостатки:

  • Нет информации о размере
  • Нельзя присваивать
  • Небезопасны при передаче в функции

std::array (C++11)

Безопасная замена C-массива с фиксированным размером:

#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
size_t n = arr.size(); // 5

Преимущества:

  • Размер известен во время компиляции
  • Поддерживает итераторы
  • Может быть скопирован

std::vector

Динамический массив с автоматическим управлением памятью:

std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.resize(10);

Методы:

  • .size() — текущее количество элементов
  • .capacity() — выделенная память
  • .reserve(n) — предварительное выделение памяти
  • .shrink_to_fit() — уменьшение capacity до size

Строки

C-строки: массив char с завершающим нулём ('\0')

const char* s = "Hello";

std::string: безопасная, динамическая строка

std::string s = "Hello";
s += " World";
s.size(); // 11

Методы:

  • .c_str() — возвращает C-строку
  • .data() — указатель на данные (C++17: всегда завершён \0)
  • .substr(pos, len) — подстрока
  • .find(), .replace(), .erase() — манипуляции

std::string_view (C++17): невладеющий «вид» на строку

void process(std::string_view sv); // принимает string, C-string, литерал

Работа с памятью

Выделение памяти

  • new / delete — для отдельных объектов
  • new[] / delete[] — для массивов
  • malloc / free — из C, не вызывают конструкторы/деструкторы

Размещение (placement new)

Конструирование объекта в уже выделенной памяти:

alignas(MyClass) char buffer[sizeof(MyClass)];
MyClass* obj = new(buffer) MyClass(); // placement new
obj->~MyClass(); // явный вызов деструктора

Используется в аллокаторах, пулах памяти, embedded-системах.

Выравнивание

  • alignof(T) — требуемое выравнивание типа T
  • alignas(N) — принудительное выравнивание переменной или структуры

Пример:

struct alignas(16) Vec4 {
float x, y, z, w;
};

Аллокаторы

Стандартные контейнеры принимают аллокаторы:

std::vector<int, MyAllocator<int>> v;

По умолчанию используется std::allocator<T>.


Основы потокобезопасности

C++11 ввёл стандартную поддержку многопоточности.

Потоки (std::thread)

#include <thread>
void task() { /* ... */ }
std::thread t(task);
t.join(); // ждать завершения

Передача аргументов:

std::thread t([](int x) { std::cout << x; }, 42);

Мьютексы (std::mutex)

Защита общих данных:

std::mutex mtx;
mtx.lock();
// критическая секция
mtx.unlock();

Лучше использовать RAII-обёртки:

std::lock_guard<std::mutex> lock(mtx); // автоматическая блокировка/разблокировка

Или std::scoped_lock (C++17) — для нескольких мьютексов без deadlock.

Атомарные операции

Для простых типов:

#include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1); // потокобезопасное увеличение

Гарантирует, что операция выполняется целиком, без прерываний.

Future и promise

Асинхронный результат:

std::future<int> fut = std::async([]() { return 42; });
int result = fut.get(); // блокирующее ожидание

Стандартная библиотека: контейнеры

Контейнеры — классы, хранящие наборы объектов. Все объявлены в заголовках <vector>, <list>, <map> и других.

Последовательные контейнеры

КонтейнерХарактеристикиСложность доступаИспользование
std::array<T, N>Фиксированный размер, стек/статикаO(1)Замена C-массивов
std::vector<T>Динамический массивO(1)Основной контейнер для большинства задач
std::deque<T>Двусторонняя очередьO(1) на концахЭффективные push_front/push_back
std::list<T>Двусвязный списокO(n)Частые вставки/удаления в середине
std::forward_list<T>Односвязный списокO(n)Минимальное потребление памяти

Особенности:

  • vector — предпочтительный выбор по умолчанию
  • list не поддерживает произвольный доступ (operator[])
  • Все контейнеры поддерживают итераторы

Ассоциативные контейнеры (упорядоченные)

КонтейнерКлючУникальностьРеализацияСложность
std::set<Key>Keyуникальныекрасно-чёрное деревоO(log n)
std::multiset<Key>Keyдубликаты разрешеныто жеO(log n)
std::map<Key, T>KeyTуникальные ключито жеO(log n)
std::multimap<Key, T>KeyTдубликаты ключейто жеO(log n)

Упорядочены по ключу (по умолчанию — оператор <).

Неупорядоченные контейнеры (хэш-таблицы, C++11)

КонтейнерКлючУникальностьСредняя сложность
std::unordered_set<Key>KeyуникальныеO(1)
std::unordered_multiset<Key>KeyдубликатыO(1)
std::unordered_map<Key, T>KeyTуникальные ключиO(1)
std::unordered_multimap<Key, T>KeyTдубликаты ключейO(1)

Требования:

  • Для ключа должен быть определён std::hash<Key> или пользовательский хэш
  • Порядок элементов не гарантируется

Контейнеры-адаптеры

  • std::stack<T> — LIFO, основан на deque по умолчанию
  • std::queue<T> — FIFO, основан на deque
  • std::priority_queue<T> — макс-куча, основан на vector

Пример:

std::priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
// pq.top() == 4

Итераторы

Интерфейс для обхода контейнеров. Классифицируются по возможностям:

ТипВозможности
Input Iteratorчтение, однократный проход вперёд
Output Iteratorзапись, однократный проход вперёд
Forward Iteratorмногократный проход вперёд, чтение/запись
Bidirectional Iteratorвперёд и назад (--it)
Random Access Iteratorпроизвольный доступ (it + n, it[n], сравнение)

Соответствие контейнеров:

  • vector, deque, array → random access
  • list, set, map → bidirectional
  • unordered_* → forward

Алгоритмы принимают пары итераторов: [first, last)

Полезные функции:

  • begin(c), end(c) — итераторы начала и конца
  • cbegin(c), cend(c) — константные итераторы
  • rbegin(c), rend(c) — обратные итераторы

Алгоритмы (<algorithm>)

Работают с диапазонами через итераторы.

Немодифицирующие

  • std::find(first, last, value) — поиск значения
  • std::count(first, last, value) — подсчёт вхождений
  • std::all_of, any_of, none_of — проверка условий
  • std::for_each(first, last, func) — применение функции к каждому элементу

Модифицирующие

  • std::copy, std::move — копирование/перемещение
  • std::fill, std::replace, std::transform
  • std::generate — заполнение с помощью генератора

Сортировка и упорядочение

  • std::sort(first, last) — быстрая сортировка (не стабильна)
  • std::stable_sort — стабильная сортировка
  • std::partial_sort — частичная сортировка
  • std::nth_element — нахождение n-го элемента без полной сортировки

Поиск и сравнение

  • std::binary_search — в отсортированном диапазоне
  • std::equal, std::lexicographical_compare
  • std::mismatch — первое различие

Диапазоны (C++20)

Новый интерфейс без явных итераторов:

#include <ranges>
auto evens = vec | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * 2; });

Ленивые, компонуемые, безопасные.


Функциональные возможности

std::function

Обобщённая обёртка над callable-объектами:

#include <functional>
std::function<int(int, int)> op = [](int a, int b) { return a + b; };
int result = op(3, 4); // 7

Может хранить:

  • Функции
  • Лямбды
  • Объекты с operator()
  • Связанные функции (std::bind)

std::bind (устаревает в пользу лямбд)

Фиксация аргументов функции:

void print(int a, int b, int c) { /* ... */ }
auto partial = std::bind(print, 1, std::placeholders::_1, 3);
partial(2); // вызовет print(1, 2, 3)

Современная замена — лямбда:

auto partial = [](int x) { print(1, x, 3); };

Работа со временем (<chrono>)

Типобезопасная работа с длительностями и моментами времени.

Длительности

std::chrono::seconds sec(5);
std::chrono::milliseconds ms = sec; // 5000 мс
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(ms);

Типы:

  • nanoseconds, microseconds, milliseconds
  • seconds, minutes, hours

Часы

  • std::chrono::system_clock — системное время (можно преобразовать в time_t)
  • std::chrono::steady_clock — монотонные часы (подходят для замера интервалов)
  • std::chrono::high_resolution_clock — самый точный таймер

Пример замера:

auto start = std::chrono::steady_clock::now();
// ... код ...
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

Временные точки

auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::cout << std::ctime(&t);

Потоки ввода-вывода

Основные потоки (<iostream>)

  • std::cin — стандартный ввод
  • std::cout — стандартный вывод
  • std::cerr — ошибки (небуферизованный)
  • std::clog — логирование (буферизованный)

Форматирование:

std::cout << std::hex << 255; // выведет "ff"
std::cout << std::setw(10) << std::setfill('0') << 42; // "0000000042"

Файловые потоки (<fstream>)

  • std::ifstream — чтение из файла
  • std::ofstream — запись в файл
  • std::fstream — чтение и запись

Пример:

std::ofstream file("data.txt");
file << "Hello\n";
file.close();

Режимы открытия:

  • std::ios::in, out, app, binary, trunc

Строковые потоки (<sstream>)

  • std::stringstream — работа со строками как с потоками
  • std::istringstream, std::ostringstream — только ввод/вывод

Пример парсинга:

std::string data = "123 45.6 true";
std::istringstream ss(data);
int i; double d; bool b;
ss >> i >> d >> std::boolalpha >> b;

Локализация и кодировки

Локали (<locale>)

Управление региональными настройками:

std::cout.imbue(std::locale("ru_RU.UTF-8"));

Влияет на:

  • Формат чисел (разделитель дробной части)
  • Сравнение строк (collate)
  • Преобразование регистра (ctype)

Кодировки

  • Исходный код обычно в UTF-8
  • Строковые литералы:
    • "..." — узкие строки (char), интерпретация зависит от системы
    • u8"..." — UTF-8 (char)
    • u"..." — UTF-16 (char16_t)
    • U"..." — UTF-32 (char32_t)
    • L"..." — широкие строки (wchar_t)

Для переносимой работы с Unicode рекомендуется:

  • Хранить текст в std::string как UTF-8
  • Использовать сторонние библиотеки (например, ICU) для сложных операций
  • Избегать wchar_t вне Windows API

Модули (C++20)

Модули — современная замена заголовочным файлам, устраняющая проблемы с препроцессором, дублированием компиляции и утечкой приватных деталей.

Объявление модуля

Файл math.ixx (расширение зависит от компилятора):

export module math;

export int add(int a, int b) {
return a + b;
}

// Неэкспортированная функция — видна только внутри модуля
int helper() { return 42; }

Использование модуля

import math;

int main() {
int result = add(3, 4); // OK
// helper(); // ошибка: не экспортирована
}

Разделение на интерфейс и реализацию

// math.cppm
export module math;

export int add(int, int);

module : private;
// или просто:
int internal_impl(int a, int b) { return a + b; }

int add(int a, int b) {
return internal_impl(a, b);
}

Преимущества:

  • Компиляция быстрее (нет повторного парсинга заголовков)
  • Чёткая инкапсуляция
  • Нет проблем с порядком #include
  • Нет макросов, влияющих на внешний код

Поддержка:

  • Clang ≥16, GCC ≥13, MSVC ≥19.28 (частично)

Корутины (C++20)

Корутины — функции, которые могут приостанавливать своё выполнение и возобновлять его позже.

Основные ключевые слова

  • co_await — приостановка до завершения асинхронной операции
  • co_yield — возврат значения и приостановка (генераторы)
  • co_return — завершение корутины с результатом

Пример генератора

#include <coroutine>
#include <iostream>

generator<int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}

for (int x : range(0, 5)) {
std::cout << x << " "; // 0 1 2 3 4
}

(Тип generator требует пользовательской реализации или библиотеки.)

Асинхронный вызов

task<int> fetch_data() {
auto data = co_await async_read_file("data.txt");
co_return parse(data);
}

Корутины требуют определения promise-типа, который управляет состоянием и возвратом.

Сложности:

  • Высокий порог входа
  • Требуется глубокое понимание жизненного цикла объектов
  • Поддержка в стандартной библиотеке минимальна (ожидается в C++23/26)

Метапрограммирование и обобщённое программирование

constexpr

Функции и переменные, вычисляемые во время компиляции:

constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}

constexpr int f = factorial(5); // вычислено на этапе компиляции

C++14+: тело constexpr-функции может содержать циклы, локальные переменные, ветвления.

consteval (C++20)

Гарантированно вычисляется только во время компиляции:

consteval int square(int x) { return x * x; }
// square(runtime_value); // ошибка компиляции

constinit (C++20)

Гарантирует инициализацию во время компиляции (для глобальных переменных):

constinit static int x = 42;

if constexpr (C++17)

Условная компиляция внутри шаблонов:

template <typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
// этот блок исчезает, если T — не целое
std::cout << "Integer: " << value << "\n";
} else {
std::cout << "Other: " << value << "\n";
}
}

Позволяет избежать SFINAE в простых случаях.

SFINAE (Substitution Failure Is Not An Error)

Механизм, при котором недопустимая подстановка шаблонных аргументов не вызывает ошибку, а просто исключает функцию из перегрузки.

Пример:

template <typename T>
auto has_begin(T&& t) -> decltype(t.begin(), std::true_type{});

template <typename T>
std::false_type has_begin(...);

Современная замена — концепции (C++20).

Концепции (Concepts, C++20)

Ограничения на шаблонные параметры:

template <typename T>
concept Addable = requires(T a, T b) {
a + b;
};

template <Addable T>
T sum(T a, T b) {
return a + b;
}

Стандартные концепции:

  • std::integral, std::floating_point
  • std::copyable, std::movable
  • std::equality_comparable, std::totally_ordered
  • std::invocable, std::predicate

Best practices и распространённые ловушки

Правила проектирования

  • RAII: ресурсы управляются временем жизни объектов
  • Правило нуля: если можно избежать написания специальных функций-членов — не пишите их
  • Избегайте сырых указателей в интерфейсах
  • Предпочитайте const по умолчанию
  • Используйте override и final для виртуальных функций

Распространённые ошибки

  • Двойной delete или использование после delete
  • Неправильное использование delete вместо delete[]
  • Срезка объектов при передаче по значению базового класса
  • Захват this в лямбде без проверки времени жизни
  • Гонки данных при многопоточности без синхронизации
  • Неявные преобразования через однопараметрические конструкторы (используйте explicit)

Производительность

  • Избегайте ненужных копий — используйте ссылки и перемещение
  • Резервируйте память в vector, если известен размер
  • Используйте emplace_back вместо push_back для сложных типов
  • Избегайте виртуальных вызовов в горячих циклах без профилирования

Инструменты разработки

Сборка

  • CMake — кроссплатформенная система сборки:
    add_executable(app main.cpp)
    target_compile_features(app PRIVATE cxx_std_20)
  • Ninja, Make, MSBuild — генераторы

Компиляторы

  • GCC: -std=c++20 -Wall -Wextra -O2 -g
  • Clang: аналогично, с отличной диагностикой
  • MSVC: /std:c++20 /W4 /permissive-

Санитайзеры (sanitizers)

  • AddressSanitizer (ASan) — утечки, выход за границы
  • UndefinedBehaviorSanitizer (UBSan) — неопределённое поведение
  • ThreadSanitizer (TSan) — гонки данных

Включение (GCC/Clang):

g++ -fsanitize=address -g -O1 app.cpp

Профилирование

  • perf (Linux)
  • VTune, Valgrind, gprof
  • Встроенные профайлеры в Visual Studio, CLion

Статический анализ

  • Cppcheck, PVS-Studio, Clang-Tidy
  • Интеграция в CI для контроля качества

Форматирование

  • clang-format — автоматическое форматирование по конфигурации
  • Единый стиль в команде снижает когнитивную нагрузку