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

Идиомы современного 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
Скрыть реализацию в .soPIMPL
Опциональный результатoptional

Дальше

  • Конвейеры данных без лишних копий: 31
  • Vulkan и дескрипторы GPU: 29
  • Самопроверка: 999, задачи: 1008

См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).