Ключевое слово 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() должен вернуть все содержимое, за которым следует "...".
Попробуйте сами
#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Итоги — Обобщенный контейнер