Не снимайте `const` приведением типа
Причина
Это делает const ложью. Если переменная объявлена как const, её модификация приводит к неопределённому поведению.
Пример (плохой)
void f(const int& x)
{
const_cast<int&>(x) = 42; // ПЛОХО
}
static int i = 0;
static const int j = 0;
f(i); // скрытый побочный эффект
f(j); // неопределённое поведение
Пример
Иногда возникает соблазн использовать const_cast, чтобы избежать дублирования кода — например, когда две функции-аксессора, отличающиеся только константностью, имеют аналогичные реализации:
class Bar;
class Foo {
public:
// ПЛОХО, дублирует логику
Bar& get_bar()
{
/* сложная логика получения неконстантной ссылки на my_bar */
}
const Bar& get_bar() const
{
/* та же сложная логика получения константной ссылки на my_bar */
}
private:
Bar my_bar;
};
Вместо этого лучше использовать общую реализацию. Обычно достаточно вызвать из неконстантной версии константную. Однако при сложной логике это может привести к следующему паттерну, всё равно прибегающему к const_cast:
class Foo {
public:
// не идеально: неконстантная версия вызывает константную, но прибегает к const_cast
Bar& get_bar()
{
return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
}
const Bar& get_bar() const
{
/* сложная логика получения константной ссылки на my_bar */
}
private:
Bar my_bar;
};
Хотя этот паттерн безопасен при правильном применении (поскольку вызывающий код изначально имел неконстантный объект), он не идеален: безопасность сложно гарантировать автоматически как правило проверки.
Лучше вынести общий код в вспомогательную функцию-шаблон, которая выводит константность. Это полностью исключает const_cast:
class Foo {
public: // хорошо
Bar& get_bar() { return get_bar_impl(*this); }
const Bar& get_bar() const { return get_bar_impl(*this); }
private:
Bar my_bar;
template<class T> // хорошо: выводит, является ли T константным или нет
static auto& get_bar_impl(T& t)
{ /* сложная логика получения возможно константной ссылки на my_bar */ }
};
Примечание: не выполняйте в шаблоне большую независимую работу — это ведёт к раздуванию кода. Дальнейшим улучшением может быть вынесение части get_bar_impl, не зависящей от шаблона, в обычную функцию.
Исключение
Может потребоваться снять const при вызове функций, некорректно игнорирующих константность. Предпочтительнее обернуть такие функции в корректные по константности обёртки-обёртки, чтобы приведение находилось в одном месте.
Пример
Иногда «снятие const» нужно для обновления некоторой переходной информации в объекте, неизменном в целом. Примерами служат кэширование, мемоизация и предвычисления. Такие случаи зачастую лучше решаются с помощью mutable или косвенного доступа, а не const_cast.
Рассмотрим хранение ранее вычисленных результатов для дорогостоящей операции:
int compute(int x); // вычислить значение для x; предположим, это дорого
class Cache { // некий тип, реализующий кэш для операции int->int
public:
pair<bool, int> find(int x) const; // есть ли значение для x?
void set(int x, int v); // сохранить v как значение для x
// ...
private:
// ...
};
class X {
public:
int get_val(int x)
{
auto p = cache.find(x);
if (p.first) return p.second;
int val = compute(x);
cache.set(x, val); // записать значение для x
return val;
}
// ...
private:
Cache cache;
};
Здесь get_val() логически константен, и хотелось бы сделать его константным членом. Для этого необходимо изменять cache, поэтому иногда прибегают к const_cast:
class X { // подозрительное решение на основе приведения
public:
int get_val(int x) const
{
auto p = cache.find(x);
if (p.first) return p.second;
int val = compute(x);
const_cast<Cache&>(cache).set(x, val); // некрасиво
return val;
}
// ...
private:
Cache cache;
};
К счастью, есть лучшее решение — объявить cache как mutable, даже для константного объекта:
class X { // лучшее решение
public:
int get_val(int x) const
{
auto p = cache.find(x);
if (p.first) return p.second;
int val = compute(x);
cache.set(x, val);
return val;
}
// ...
private:
mutable Cache cache;
};
Альтернативное решение — хранить указатель на cache:
class X { // OK, но немного грязнее
public:
int get_val(int x) const
{
auto p = cache->find(x);
if (p.first) return p.second;
int val = compute(x);
cache->set(x, val);
return val;
}
// ...
private:
unique_ptr<Cache> cache;
};
Это наиболее гибкое решение, но требует явной конструкции и деструкции *cache (скорее всего, в конструкторе и деструкторе X).
В любом варианте необходимо защищаться от гонок данных на cache в многопоточном коде, возможно с помощью std::mutex.
Контроль
- Отмечать использование
const_cast. - Данное правило входит в профиль типобезопасности.