Ключевое слово mutable
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 30 из 104.
Вы узнали, что константные функции-члены не могут изменять никакие переменные-члены. Но иногда вам нужно изменить член класса, который не влияет на логическое состояние объекта — например, кэш или счетчик обращений. Ключевое слово mutable предоставляет исключение из ограничения const.
Член класса с модификатором mutable может быть изменен даже внутри константной функции-члена:
class DataFetcher {
std::string data;
mutable int accessCount; // Может быть изменен в константных функциях
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Разрешено, так как accessCount помечен как mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Без mutable инкрементирование accessCount внутри константной функции getData() вызвало бы ошибку компиляции. Ключевое слово mutable сообщает компилятору: «Этому члену разрешено изменяться, даже если объект считается константным».
Распространенные сценарии использования mutable включают кэширование вычисленных значений, отслеживание статистики доступа и примитивы синхронизации, такие как мьютексы. В каждом из этих случаев модификация не меняет того, что объект представляет логически: кэшированное значение — это всего лишь оптимизация, а счетчик доступа — это метаданные об использовании.
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};Используйте mutable умеренно — его следует применять только к тем членам, которые не влияют на наблюдаемое состояние объекта.
Задание
ЛегкоДавайте создадим программу для просмотра документов, которая отслеживает, сколько раз документ был просмотрен, не нарушая константную корректность (const-correctness) наших методов, предназначенных только для чтения. Это идеальный случай для использования ключевого слова mutable.
Вы создадите два файла для организации вашего кода:
Document.h: Определите классDocument, который хранит содержимое и скрытно отслеживает статистику просмотров. Вашему классу необходимы:- Приватные члены:
title(string),content(string) иmutable int viewCount, который можно изменять даже в константных методах - Конструктор, который принимает заголовок и содержимое, инициализируя
viewCountзначением0 - Константный метод
getTitle(), который возвращает заголовок - Константный метод
getContent(), который увеличиваетviewCount(это разрешено, так как переменная помечена как mutable) и возвращает содержимое - Константный метод
getViewCount(), который возвращает количество обращений к содержимому - Константный метод
getPreview(), который возвращает первые 20 символов содержимого, за которыми следует"..."— это НЕ должно увеличивать счетчик просмотров, так как это всего лишь предварительный просмотр
- Приватные члены:
main.cpp: Продемонстрируйте, как mutable позволяет отслеживать статистику доступа, сохраняя методы логически константными. Считайте заголовок и содержимое документа из входных данных (каждое на отдельной строке), затем:- Создайте объект
Documentс входными значениями - Выведите
"Title: <title>" - Выведите
"Preview: <preview>" - Выведите
"Views after preview: <count>" - Получите полное содержимое с помощью
getContent()и выведите"Content: <content>" - Выведите
"Views after first read: <count>" - Снова обратитесь к содержимому и выведите
"Content: <content>" - Выведите
"Views after second read: <count>" - Создайте вспомогательную функцию
void displayDocument(const Document& doc), которая выводит"[Const Access] <title>: <content>"— это докажет, что наши константные методы работают с константными ссылками - Вызовите
displayDocument()для вашего документа - Выведите
"Final view count: <count>"
- Создайте объект
Счетчик просмотров должен быть равен 0 после предварительного просмотра (так как превью не считается полным просмотром), а затем увеличиваться при каждом вызове getContent(). Ключевое слово mutable делает это возможным — без него вы не смогли бы изменять viewCount внутри константных методов, таких как getContent().
Если содержимое короче 20 символов, getPreview() должен вернуть все содержимое, за которым следует "...".
Шпаргалка
Ключевое слово mutable позволяет изменять переменную-член даже внутри константных функций-членов:
class DataFetcher {
std::string data;
mutable int accessCount; // Может быть изменено в константных функциях
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Разрешено, так как accessCount помечен как mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Без mutable изменение переменной-члена внутри константной функции привело бы к ошибке компиляции. Это ключевое слово сообщает компилятору, что данному члену разрешено изменяться, даже когда объект считается константным.
Распространенные варианты использования включают кэширование вычисляемых значений, отслеживание статистики доступа и примитивы синхронизации. Модификация не должна менять то, что объект представляет логически:
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};Используйте mutable экономно — только для тех членов, которые не влияют на наблюдаемое состояние объекта.
Попробуйте сами
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: Реализуйте функцию displayDocument
// Она должна выводить: "[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: Реализуйте эту функцию
}
int main() {
// Считать входные данные
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: Создайте Document с входными значениями
// TODO: Выведите "Title: <title>"
// TODO: Выведите "Preview: <preview>"
// TODO: Выведите "Views after preview: <count>"
// TODO: Получите доступ к полному содержимому и выведите "Content: <content>"
// TODO: Выведите "Views after first read: <count>"
// TODO: Снова получите доступ к содержимому и выведите "Content: <content>"
// TODO: Выведите "Views after second read: <count>"
// TODO: Вызовите displayDocument() со своим документом
// TODO: Выведите "Final view count: <count>"
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер