Ключевое слово mutable
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 30 из 104.
Вы узнали, что функции-члены const не могут изменять переменные-члены. Но иногда необходимо изменить член, который не влияет на логическое состояние объекта, — например, кэш или счётчик обращений. Ключевое слово mutable предоставляет исключение из ограничения const.
Член mutable можно изменять даже внутри константной функции-члена:
class DataFetcher {
std::string data;
mutable int accessCount; // Может быть изменён в const-функциях
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 внутри const-функции getData() вызвало бы ошибку компилятора. Ключевое слово mutable сообщает компилятору: «Этому члену разрешено изменяться, даже когда объект считается const».
Распространённые варианты использования изменяемых объектов включают кэширование вычисленных значений, отслеживание статистики обращений и примитивы синхронизации, такие как мьютексы. В каждом случае изменение не меняет того, что объект логически представляет собой, — кэшированное значение является лишь оптимизацией, а счётчик обращений — метаданными об использовании.
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-корректность наших методов только для чтения. Это идеальный вариант использования ключевого слова mutable.
Вы создадите два файла, чтобы организовать код:
Document.h: ОпределитеDocumentclass, который хранит содержимое и незаметно отслеживает статистику просмотров. Вашему class необходимы:- Закрытые члены:
title(строка),content(строка) иmutable int viewCount, который можно изменять даже в const-методах - Конструктор, принимающий заголовок и содержимое и инициализирующий
viewCountзначением0 - Const-метод
getTitle(), возвращающий заголовок - Const-метод
getContent(), увеличивающийviewCount(это разрешено, потому что он mutable) и возвращающий содержимое - Const-метод
getViewCount(), возвращающий количество обращений к содержимому - Const-метод
getPreview(), возвращающий первые 20 символов содержимого, за которыми следует"...". Этот метод НЕ должен увеличивать счётчик просмотров, поскольку это всего лишь предварительный просмотр
- Закрытые члены:
main.cpp: Продемонстрируйте, как mutable позволяет отслеживать статистику обращений, сохраняя логическую константность методов. Считайте заголовок и содержимое документа из input (каждое значение находится на отдельной строке), затем:- Создайте
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>". Это подтверждает, что наши const-методы работают с const-ссылками - Вызовите
displayDocument()для своего документа - Выведите
"Final view count: <count>"
- Создайте
Счётчик просмотров должен иметь значение 0 после предварительного просмотра (поскольку предварительные просмотры не считаются полными просмотрами), а затем увеличиваться при каждом вызове getContent(). Ключевое слово mutable делает это возможным; без него вы не смогли бы изменять viewCount внутри const-методов, таких как getContent().
Если содержимое короче 20 символов, getPreview() должен возвращать всё содержимое, за которым следует "...".
Попробуйте сами
#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: Print "Views after preview: <count>"
// TODO: Получите полный контент и выведите "Content: <content>"
// TODO: Print "Views after first read: <count>"
// TODO: Снова получите контент и выведите "Content: <content>"
// TODO: Print "Views after second read: <count>"
// TODO: Вызовите displayDocument() с вашим документом
// TODO: Print "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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++