January 18, 2021

Ключевое слово mutable

Про ключевое слово mutable знают немногие разработчики, однако оно может быть очень полезным в ряде случаев. В этой статье мы рассмотрим случай, когда семантическая константность не эквивалентна синтаксической константности, и ключевое слово mutable позволит решить эту проблему. Но давайте обо всем по-порядку...

Бывают ситуации, когда нам нужно изменить состояние объекта, при этом сохранив видимое извне состояние объекта константным. Изменение объекта может понадобиться по каким-либо техническим причинам, но клиенты (пользователи объекта) не должны этого заметить. Ярким примером такого рода задач является кеширование данных.

Рассмотрим класс Date:

class Date {
	int day;
	int month;
	int year;
public:
	Date(int d, int m, int y) : day(d), month(m), year(y) { }
	string toString() const {
		convertToString();
	}
	void setDate(int d, int m, int y){
	    day = d;
	    month = m;
	    year = y;
    }
	// ...
};

Предположим, что метод convertToString- это ресурсозатратная операция, которую мы не хотим вызывать каждый раз при вызове метода toString. Если дата не поменялась, то зачем вычислять строковое представление даты опять?! Можно один раз сохранить строку в переменной и возвращать ее каждый раз до тех пор, пока дата не поменяется. В результате у нас будет кеширование строки и очистка кеша при изменении даты.

class Date {
	int day;
	int month;
	int year;
	string cache = "";
public:
	Date(int d, int m, int y) : day(d), month(m), year(y) { }
	string toString() const {
    	if(cache.empty())
    		cache = convertToString();
        return cache;
	}
	void setDate(int d, int m, int y){
	    day = d;
	    month = m;
	    year = y;
	    resetCache();
    }
    void resetCache(){
        cache = "";
    }
	// ...
};

Вроде бы все хорошо. Мы кешировали строковое представление даты и теперь вызываем функцию convertToString только тогда, когда это действительно необходимо. Однако есть проблема... Этот код не скомпилируется! Дело в том, что метод toString константный, но он изменяет переменную cache. Если убрать const из объявления метода, то клиенты нашего класса не поймут. Ведь toString - это простой геттер, данная функция точно не должна ничего менять в классе. Почему же тогда нет const в ее объявлении?

В этой проблеме нам поможет ключевое слово mutable. Оно добавляется в объявлении к переменным членам класса и указывает, что данная переменная может изменяться в константном методе. Вот готовый пример:

class Date {
	int day;
	int month;
	int year;
	mutable string cache = "";
public:
	Date(int d, int m, int y) : day(d), month(m), year(y) { }
	string toString() const {
    	if(cache.empty())
    		cache = convertToString();
        return cache;
	}
	void setDate(int d, int m, int y){
	    day = d;
	    month = m;
	    year = y;
	    resetCache();
    }
    void resetCache(){
        cache = "";
    }
	// ...
};

Помимо рассмотренного случая, mutable используется в лямбда-функциях для захвата переменных по значению, которые потом можно будет изменить в теле лямбды.

Ключевое слово mutable играет важную роль в написании чистого кода. Если метод в вашем классе по логике должен быть константным, ставьте const. При этом если он изменяет какие-то внутренние поля, то помечайте их словом mutable.