Не из лекции

На первом занятии эта тема не разбиралась. const появляется в лонгриде ровно один раз — в примере сравнения дробных чисел, без объяснения. Страница написана «на вырост»: правила именования констант требует кодстайл, так что знать это полезно уже сейчас.

const — «после инициализации не меняется». constexpr — «значение известно уже на этапе компиляции». Второе сильнее первого: всякая constexpr-переменная является и const.

const int size = ReadSize();       // значение узнаем во время работы, менять нельзя
constexpr int kMaxSize = 100;      // известно компилятору

Зачем

Читателю. const у переменной сообщает: дальше по коду её значение не изменится, можно не выискивать присваивания.

Компилятору. constexpr можно использовать там, где требуется константное выражение: размер массива, параметр шаблона, case в switch.

constexpr int kBufferSize = 256;
int buffer[kBufferSize];           // так можно

Себе. Именованная константа вместо числа посреди выражения — это ещё и объяснение, что число означает:

constexpr int kSecondsInHour = 3600;
int hours = total_seconds / kSecondsInHour;   // понятно без комментария

Такие «голые» числа называют магическими (magic numbers), и clang-tidy на них ругается проверкой readability-magic-numbers. Исключение — 0 и 1.

Именование

По кодстайлу курса константы пишутся в kCamelCase: kMaxSize, kSecondsInMinute, kEpsilon. Префикс k — от немецкого Konstante, прижился через Google Style Guide. Правило распространяется на глобальные константы, константы пространства имён и статические константные поля класса.

const-корректность

Привычка помечать const всё, что не меняется, называется const-correctness. Проверка misc-const-correctness в clang-tidy предлагает добавить const к переменным, которые вычисляются один раз:

const int total_time = hours * kSecondsInHour + minutes * kSecondsInMinute;

Дальше по коду видно: это значение окончательное.

Тонкости

  • const у переменной не означает, что значение известно заранее: оно может прийти из std::cin.
  • constexpr требует, чтобы инициализатор вычислялся компилятором, — вызов обычной функции туда не поставить.
  • Для указателей важно, что именно константа: const int* p — нельзя менять значение по указателю, int* const p — нельзя менять сам указатель. Это отдельная тема, всплывёт вместе с указателями.
  • constexpr бывает и у функций, тогда их можно вычислять на этапе компиляции.

Частые ошибки

  • Объявить константу без k и получить замечание на ревью.
  • Магическое число «на один раз» — оно почти всегда встречается ещё раз, и тогда одно из мест забывают поправить.
  • Ждать от const защиты от изменения через другой указатель или ссылку: const — договорённость, а не охрана памяти.

Углубление — L03

Обещанная тема с указателями разобрана в L03. Константным бывает и объект, на который указывают, и сам указатель:

const int* px = ...;        // указатель на константу
int const* py = ...;        // то же самое
int* const pz = ...;        // константный указатель
const int* const pa = ...;  // константный указатель на константу

Типы указателей читаются справа налево. Указатель на константу можно взять и от неконстантного объекта — тогда его нельзя менять только через этот указатель. Обратное запрещено: float* py = &y при const float y — ошибка компиляции, иначе константу можно было бы изменить через указатель. Подробно — Указатель.