По умолчанию делайте функции-члены `const`
Причина
Функция-член должна быть помечена const, если она не изменяет наблюдаемое состояние объекта. Это даёт более точное выражение замысла проектирования, повышает читаемость, позволяет компилятору обнаруживать больше ошибок и иногда открывает дополнительные возможности для оптимизации.
Пример (плохой)
class Point {
int x, y;
public:
int getx() { return x; } // ПЛОХО: должна быть const, поскольку не изменяет состояние объекта
// ...
};
void f(const Point& pt)
{
int x = pt.getx(); // ОШИБКА: не компилируется, потому что getx не помечена const
}
Примечание
Передача указателя или ссылки на не-const сама по себе не является плохой практикой, но это следует делать только тогда, когда вызываемая функция должна изменять объект. Читатель кода должен предполагать, что функция, принимающая «обычный» T* или T&, изменяет упоминаемый объект. Если сейчас она не делает этого, то может сделать позже без принудительной перекомпиляции.
Примечание
Существует код/библиотеки, предлагающие функции, которые объявляют T*, хотя эти функции не изменяют T. Это проблема при модернизации кода. Можно:
- обновить библиотеку, сделав её
const-корректной — предпочтительное долгосрочное решение; - «снять
const»; лучше избегать этого; - предоставить функцию-обёртку.
Пример:
void f(int* p); // старый код: f() не изменяет `*p`
void f(const int* p) { f(const_cast<int*>(p)); } // обёртка
Обратите внимание, что это решение является заплаткой и должно применяться только тогда, когда объявление f() нельзя изменить, например потому что оно находится в библиотеке, которую вы не можете изменить.
Примечание
Функция-член const может изменять значение объекта, помеченного как mutable, или объекта, доступного через указатель-член. Распространённый случай — поддержка кэша вместо повторного выполнения сложных вычислений. Например, вот класс Date, который кэширует (мемоизирует) своё строковое представление для упрощения повторного использования:
class Date {
public:
// ...
const string& string_ref() const
{
if (string_val == "") compute_string_rep();
return string_val;
}
// ...
private:
void compute_string_rep() const; // вычислить строковое представление и поместить в string_val
mutable string string_val;
// ...
};
Иначе говоря, const-ность не является транзитивной. Функция-член const может изменять значения членов mutable и значения объектов, доступных через не-const-указатели. Задача класса — гарантировать, что подобные изменения выполняются только там, где это осмысленно с точки зрения семантики (инвариантов), предлагаемых пользователям.
Смотрите также: Pimpl
Контроль
- Помечать функции-члены, не отмеченные
const, которые при этом не выполняют не-const-операций ни с одним из членов данных.