Идиомы современного C++
Идиома — устойчивый приём, который команда узнаёт по имени. В C++ они связывают владение ресурсами, копирование и исключения. Подробности по памяти: 19, по ООП: 14, по стилю проекта: 101.
RAII (Resource Acquisition Is Initialization)
Ресурс захватывается в конструкторе, освобождается в деструкторе. Работает при любом выходе из области видимости, в том числе через исключение.
class ScopedFile {
std::FILE* fp_{};
public:
explicit ScopedFile(const char* path, const char* mode)
: fp_(std::fopen(path, mode)) {
if (!fp_) throw std::runtime_error("open failed");
}
~ScopedFile() { if (fp_) std::fclose(fp_); }
ScopedFile(const ScopedFile&) = delete;
ScopedFile& operator=(const ScopedFile&) = delete;
};
Умные указатели — RAII для кучи: unique_ptr, shared_ptr, lock_guard для мьютексов.
| Ресурс | Типичная обёртка |
|---|---|
| Динамическая память | std::unique_ptr |
| Разделяемая память | std::shared_ptr + weak_ptr против циклов |
| Мьютекс | std::lock_guard, std::unique_lock |
| Файл / сокет | свой класс или библиотека |
Rule of Zero / Three / Five
Если класс не владеет уникальным ресурсом (только значения и стандартные контейнеры), не объявляйте деструктор, копирование и перемещение вручную — компилятор сгенерирует корректные операции (Rule of Zero).
Если владеете (сырой указатель, дескриптор, FILE*), нужны пять специальных функций (или явный = delete):
- деструктор;
- копирующий конструктор и оператор присваивания;
- перемещающий конструктор и оператор присваивания (C++11+).
Иначе — двойное освобождение, утечки, «полураспадающие» объекты после std::move.
Copy-and-swap
Безопасное присваивание через копию во временный объект и обмен:
class StringHolder {
std::size_t size_{};
char* data_{nullptr};
void swap(StringHolder& other) noexcept {
std::swap(size_, other.size_);
std::swap(data_, other.data_);
}
public:
StringHolder& operator=(StringHolder other) { // по значению
swap(other);
return *this;
}
};
Копирующий конструктор можно реализовать через *this = other. Исключение в середине присваивания не оставляет объект в «полусломанном» состоянии — сильная гарантия.
Move-and-swap
Для типов с дорогим копированием и дешёвым перемещением:
- перемещающий конструктор забирает ресурсы у источника (источник — в валидном «пустом» состоянии);
- перемещающее присваивание:
otherперемещают во временный, затемswapс*this.
Помечайте перемещение noexcept, если оно не бросает: иначе std::vector при росте выберет копирование вместо перемещения.
Идиома «удалить через алгоритм» (remove + erase)
std::remove / std::remove_if не сжимают контейнер — они сдвигают «живые» элементы в начало и возвращают итератор на новый «логический» конец. Размер нужно уменьшить вручную:
std::vector<int> v{1, 2, 3, 2, 4};
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
// v == {1, 3, 4}
В C++20 то же через ranges: Диапазоны и представления.
PIMPL (Pointer to Implementation)
Скрывает детали реализации за указателем на неполный тип — ускоряет пересборку и стабилизирует ABI библиотеки:
// widget.hpp
class Widget {
struct Impl;
std::unique_ptr<Impl> impl_;
public:
Widget();
~Widget();
void draw();
};
Деструктор объявляют в .cpp, где тип Impl полный.
Enum class и явные преобразования
enum class Status { Ok, Error }; — не смешивается с int без static_cast. Для флагов — enum class с операторами или std::byte + маски.
std::optional вместо «магических» значений
Возврат «нет результата» через std::optional<T> понятнее, чем -1, nullptr или пустая строка с особым смыслом. См. 16.
Не бросать из деструктора
Если деструктор бросает исключение при раскрутке стека — std::terminate. Деструкторы должны быть noexcept по умолчанию (для многих типов — неявно). Освобождение в деструкторе — только небросающие операции или try/catch с логированием (крайний случай).
Сводная таблица «когда что»
| Задача | Идиома |
|---|---|
| Файл, мьютекс, GPU-буфер | RAII |
Класс с new внутри | Rule of Five + умные указатели |
Оператор = с сильной гарантией | Copy-and-swap |
vector элементов с ресурсом | Move + noexcept |
| Удалить элементы по условию | remove-erase |
| Скрыть реализацию в .so | PIMPL |
| Опциональный результат | optional |
Дальше
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C++ как язык системного программирования - ключевые принципы, область применения и инженерные требования к коду. Экосистема приложений на C++ - области применения языка от системного ПО до высоконагруженных вычислений. C++ — это мощный язык программирования общего назначения, который обеспечивает прямой доступ к аппаратным ресурсам компьютера при сохранении высокой производительности. Гайд по установке и настройке с написанием первой программы и её запуском. Директива препроцессора include используется для подключения заголовочных файлов в исходный код. Она сообщает компилятору вставить содержимое указанного файла в текущее место перед началом компиляции. Конфигурация — это набор правил и переменных, которые управляют процессом превращения исходного текста в исполняемый продукт. Примеры простых и полезных консольных приложений с демонстрацией концепций языка. Минимальный кроссплатформенный проект C++17 с CMake — структура, сборка и разбор CMakeLists построчно. Модульные тесты с GTest и Catch2 в CMake-проекте — зачем отдельный target, примеры и запуск ctest. Набор мини-проектов для закрепления C++ — консоль, RAII, CMake, Qt, ranges и опционально Vulkan. Набор советов, правил, принципов и обычаев в разработке на этом языке. Типизация, набор правил определения типа данных значений языка.C++ - язык системного программирования
Экосистема приложений на C++
Что требуется знать перед началом изучения языка программирования C++
Первая программа на C++
Начало работы с C++
Конфигурация и сборка в C++
Простые приложения на C++
CMake — первая программа
Google Test и Catch2 в C++
Практические задания по C++
Рекомендации по разработке на C++
Типы данных в C++