Menu
Coddy logo textTech

Ключевое слово 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 умеренно — его следует применять только к тем членам, которые не влияют на наблюдаемое состояние объекта.

challenge icon

Задание

Легко

Давайте создадим программу для просмотра документов, которая отслеживает, сколько раз документ был просмотрен, не нарушая константную корректность (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;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование