Для чего используется ключевое слово mutable в C++ и в каких контекстах
Что проверяют
Различаете ли вы логическую и физическую константность и знаете ли оба места, где встречается mutable. Следом почти всегда спрашивают про потоки: что будет с mutable-кешем, если const-метод вызывают параллельно.
Как отвечать
Контекстов два.
Первый: член класса. Поле с mutable можно менять внутри const-методов и даже у объекта, объявленного как const. Это законно и не является неопределённым поведением, в отличие от const_cast над действительно константным объектом. Смысл в том, что const обещает неизменность наблюдаемого состояния, а служебные детали реализации под это обещание не подпадают:
- кеш и ленивое вычисление:
mutable std::optional<Hash> cached_;заполняется при первом вызовеhash() const; - синхронизация:
mutable std::mutex m_;, чтобы захватить блокировку в геттере; - счётчики и статистика: сколько раз обращались к объекту.
Ограничения: mutable нельзя повесить на static, const и ссылочные члены.
Второй: лямбды. operator() у замыкания по умолчанию const, поэтому переменные, захваченные по значению, менять нельзя. [n]() mutable { return ++n; } снимает это ограничение: меняется копия внутри объекта-замыкания, и она сохраняется между вызовами. Внешняя переменная n остаётся прежней.
Про потоки. Стандартная библиотека исходит из того, что const-методы безопасно вызывать одновременно из нескольких потоков. Если ваш const-метод пишет в mutable-кеш, эту гарантию вы обязаны обеспечить сами: std::mutex, std::atomic или std::call_once.
Где спотыкаются
Используют mutable, чтобы заглушить ошибку компиляции в const-методе, и меняют реальное состояние объекта. Тогда const в интерфейсе врёт, и вызывающий код уже не может на него опираться.
Добавляют ленивый кеш без синхронизации. Два потока одновременно заходят в get() const, оба пишут в поле, получается гонка данных.
Думают, что mutable-лямбда меняет захваченную снаружи переменную. Меняется только копия, а при копировании замыкания, например в std::function, копируется и её состояние, поэтому счётчики неожиданно раздваиваются.