Константность в C++: чек-лист для ревью и своего кода
Где ставить const, а где он бесполезен, чем const& отличается от const и как не сломать бинарную совместимость. Практический чек-лист для C++17 и новее.

Вы пишете const перед параметром типа int и радуетесь, что код «безопаснее». А компилятор молча выбрасывает этот const из сигнатуры, потому что для копии он ничего не значит. Или вешаете const на метод, а внутри дёргаете mutable-поле кеша — и метод «константный» только на бумаге. Константность в C++ — это не украшение, а часть контракта: она меняет разрешаемые операции, влияет на выбор перегрузки и на бинарную совместимость. Ниже — чек-лист, что проверять в своём коде и на ревью. Ориентир — C++17 и новее; отдельно отмечаю, что появилось в C++20 и C++23.
Четыре разных const, которые путают
Прежде чем ставить галочки, разведите четыре ситуации. Они пишутся похоже, а значат разное.
- const у параметра-значения —
void f(const int n). Влияет только на тело функции, из сигнатуры отбрасывается.void f(int)иvoid f(const int)— одна и та же функция, второе объявление не перегрузка. - const у ссылки или указателя на параметр —
void f(const std::string& s). Обещание не менять то, на что смотрим. Часть контракта, видна вызывающему. - const-метод —
int size() const. Обещание не менять наблюдаемое состояние объекта. Влияет на перегрузку и на то, можно ли звать метод уconst-объекта. - constexpr / consteval — вычислимость на этапе компиляции. Это отдельная тема, здесь её не разбираем.
const у параметра по значению — почти всегда шум
Такой const не виден вызывающему коду и не мешает никому передать что угодно. Он лишь запрещает менять локальную копию внутри функции. Иногда это полезно как защита от опечатки вроде if (n = 0), но обычно захламляет сигнатуру. В заголовке его лучше не писать: объявление void f(const int) и определение void f(int) совпадают, и разнобой сбивает читателя.
// Шум в заголовке — const отбрасывается из сигнатуры
void setTimeout(const int seconds); // объявление
void setTimeout(int seconds) { ... } // то же самое
// Осмысленно: защита копии в теле, const только в .cpp
void process(int count) {
const int total = count * 2; // дальше total не меняется — фиксируем
// ...
}
Чек-лист по параметрам функций
Как передавать аргумент, зависит от размера типа и от того, нужна ли функции копия.
| Ситуация | Как принимать | Почему |
|---|---|---|
| Дешёвый тип (int, double, указатель, enum) | T value | Копия дешевле разыменования ссылки, const T& тут лишний |
| Тяжёлый объект, читаем и не сохраняем | const T& value | Нет копии, нет случайных мутаций |
| Тяжёлый объект, функции нужна своя копия | T value и затем std::move | Даёт вызывающему выбор: копировать или отдать rvalue |
| Строковый литерал / срез без владения | std::string_view | Не создаёт std::string, не требует const& |
| Out-параметр | T& (без const) или возврат значения | const тут запретил бы запись — то, ради чего параметр и заведён |
Отдельно: const std::string& для параметра, который вы всё равно копируете внутрь поля, — устаревший приём. Если функция сохраняет аргумент, берите по значению и перемещайте — тогда вызов с временным объектом обойдётся без лишней копии.
Чек-лист по методам класса
Здесь const работает жёстче всего и чаще всего ошибается.
- Метод не меняет наблюдаемое состояние — помечайте
const. Геттеры,size(),empty(), любые запросы. Если забыть, вызвать метод уconst-объекта или черезconst-ссылку не получится, и код сломается там, где вы этого не ждёте. - Кеш и ленивая инициализация — через
mutable. Поле, которое хранит закешированный результат, помечаетсяmutable, тогда его можно менять изconst-метода. Это законно ровно потому, что наблюдаемое состояние не меняется — снаружи объект тот же. - Возврат из
const-метода тоже должен бытьconst. Если геттер отдаёт ссылку на внутреннее поле, уconst-версии этоconst T&. Иначе через возвращённую неконстантную ссылку обойдут защиту. - Парные перегрузки const/non-const. Классика —
operator[]у контейнера: одна версия возвращаетT&, втораяconst T&и помеченаconst. Компилятор выберет по константности объекта.
#include <string>
#include <vector>
class Report {
public:
// Запрос — const. Отдаём const-ссылку, чтобы поле не правили снаружи
const std::string& title() const { return title_; }
// Парные перегрузки доступа к строке
std::string& line(std::size_t i) { return lines_[i]; }
const std::string& line(std::size_t i) const { return lines_[i]; }
// Ленивый кеш: сам метод const, mutable-поле можно менять
std::size_t charCount() const {
if (!cached_) {
count_ = title_.size();
for (const auto& l : lines_) count_ += l.size();
cached_ = true;
}
return count_;
}
private:
std::string title_;
std::vector<std::string> lines_;
mutable std::size_t count_ = 0; // кеш
mutable bool cached_ = false;
};
const-метод обещает не то, что внутри ничего не поменяется, а то, что снаружи объект будет выглядеть неизменным. mutable — легальный способ сдержать это обещание при кешировании, а не лазейка «отключить const».
const и модель памяти: где легко ошибиться
const на верхнем уровне у указателей
Читается справа налево от имени. const char* p — указатель на константные данные (сами данные не меняем, указатель — можем). char* const p — константный указатель на изменяемые данные. const char* const p — оба. На ревью это частый источник путаницы: спрашивайте не «const или нет», а «что именно нельзя менять — указатель или то, на что он смотрит».
const_cast — красный флаг
Снятие const через const_cast и последующая запись в объект, который изначально объявлен как const, — неопределённое поведение. Если const_cast встречается в коде, это почти всегда сигнал, что const расставлен неправильно где-то выше по цепочке. Единственный относительно законный случай — вызов старого C-API, который берёт char*, хотя данные не меняет.
const и потоки
Стандартная библиотека рассчитывает, что const-методы безопасно звать из нескольких потоков одновременно. Если у вас const-метод пишет в mutable-поле без синхронизации, вы нарушаете это ожидание. Ленивый кеш из примера выше в многопоточном коде нужно защищать std::mutex или std::atomic, иначе получите гонку — при том что метод формально const.
Что учесть в публичном API и библиотеках
Добавление или снятие const у метода меняет его mangled-имя — то есть ломает бинарную совместимость (ABI). Если вы выпускаете разделяемую библиотеку, превращение void update() в void update() const в новой версии заставит пересобрать всех, кто линкуется с вами. На уровне исходников (API) это чаще совместимо, но не всегда: перегрузка может начать разрешаться иначе.
Для параметров: const у параметра-значения на ABI не влияет (его нет в сигнатуре), а const у ссылочного параметра — влияет, потому что это разные типы.
Что смотреть на ревью — короткий список
- Геттеры и запросы помечены
const? Если нет — почему. - Тяжёлые объекты принимаются по
const&, а не по значению без нужды. const&-параметр, который потом копируется в поле, — заменить на приём по значению сstd::move.- Есть
const_cast? Разобраться, откуда взялась потребность снятьconst. mutable-поля вconst-методе синхронизированы, если объект используется из разных потоков.- Парные перегрузки
operator[]/ доступа возвращают согласованную константность.
Что делать дальше
Включите предупреждения компилятора и статический анализ: clang-tidy умеет предлагать const там, где метод его не меняет (проверки семейства readability-* и cppcoreguidelines-*) — точный список чеков сверьте в документации вашей версии clang-tidy. Для нового кода держите правило «const по умолчанию»: делайте методы и локальные переменные константными сразу, снимайте const только когда компилятор заставит. Так константность становится не отдельной задачей на ревью, а состоянием кода по умолчанию.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: C++ Core Guidelines — Con (Constants and immutability), cppreference — const qualifier
Частые вопросы
Стоит ли писать const у параметра по значению в объявлении функции?
Чем const T& отличается от const T при передаче параметра?
У меня старая версия компилятора, будут ли отличия?
Можно ли менять mutable-поле из const-метода без последствий?
Почему после добавления const к методу перестала собираться чужая библиотека?
const_cast — это всегда ошибка?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.