Имеет ли ключевое слово 'mutable' какую-либо цель, отличную от разрешения изменять переменную с помощью функции const?
Вопрос
Некоторое время назад я наткнулся на некоторый код, который помечал переменную-член класса символом mutable
ключевое слово.Насколько я могу судить, это просто позволяет вам изменять переменную в const
способ:
class Foo
{
private:
mutable bool done_;
public:
void doSomething() const { ...; done_ = true; }
};
Это единственное использование этого ключевого слова или в нем кроется нечто большее, чем кажется на первый взгляд?С тех пор я использовал эту технику в классе, отмечая boost::mutex
как изменяемый , позволяющий const
функции блокируют его по соображениям потокобезопасности, но, честно говоря, это похоже на небольшой взлом.
Решение
Это позволяет различать побитовую const и логическую const .Логическая константа - это когда объект не изменяется таким образом, который виден через открытый интерфейс, как в вашем примере с блокировкой.Другим примером может служить класс, который вычисляет значение при первом запросе и кэширует результат.
Начиная с c ++ 11 mutable
может использоваться в лямбда-выражении для обозначения того, что объекты, захваченные значением, могут быть изменены (по умолчанию это не так).:
int x = 0;
auto f1 = [=]() mutable {x = 42;}; // OK
auto f2 = [=]() {x = 42;}; // Error: a by-value capture cannot be modified in a non-mutable lambda
Другие советы
Тот Самый mutable
ключевое слово - это способ проникнуть в const
вуаль, которую вы набрасываете на свои предметы.Если у вас есть постоянная ссылка или указатель на объект, вы не можете каким-либо образом изменить этот объект за исключением когда и как это помечается mutable
.
С вашим const
ссылка или указатель, к которым вы ограничены:
- доступ только на чтение для всех видимых элементов данных
- разрешение вызывать только методы, помеченные как
const
.
Тот Самый mutable
исключение делает это так, что теперь вы можете записывать или устанавливать помеченные элементы данных mutable
.Это единственное внешне видимое отличие.
Внутренне эти const
методы, которые видны вам, также могут выполнять запись в помеченные элементы данных mutable
.По существу, постоянная завеса прокалывается всесторонне.Это полностью зависит от разработчика API, чтобы гарантировать, что mutable
не разрушает const
концепция и используется только в полезных особых случаях.Тот Самый mutable
ключевое слово помогает, потому что оно четко выделяет элементы данных, на которые распространяются эти особые случаи.
На практике вы можете использовать const
навязчиво по всей вашей кодовой базе (по сути, вы хотите "заразить" свою кодовую базу const
"болезнь").В этом мире указателями и ссылками являются const
за очень редкими исключениями, это дает код, о котором легче рассуждать и понимать.Интересное отступление можно найти в разделе "ссылочная прозрачность".
Без mutable
ключевое слово, которое вы в конечном итоге будете вынуждены использовать const_cast
для обработки различных полезных особых случаев, которые он позволяет (кэширование, подсчет ссылок, отладочные данные и т.д.).К сожалению const_cast
значительно более разрушителен, чем mutable
потому что это вынуждает API клиент чтобы уничтожить const
защита объектов, которыми он пользуется.Кроме того, это вызывает широкое распространение const
разрушение: const_cast
использование постоянного указателя или ссылки обеспечивает беспрепятственный доступ на запись и вызов метода к видимым элементам.В отличие mutable
требует, чтобы разработчик API осуществлял детальный контроль над const
исключения, и обычно эти исключения скрыты в const
методы, работающие с личными данными.
(Н.Б.Я имею в виду данные и метод видимость несколько раз.Я говорю о членах, помеченных как public vs.частный или защищенный, что является совершенно другим типом обсуждаемой защиты объекта здесь.)
Ваше использование с boost::mutex - это именно то, для чего предназначено это ключевое слово.Другое применение - внутреннее кэширование результатов для ускорения доступа.
По сути, "изменяемый" применяется к любому атрибуту класса, который не влияет на внешне видимое состояние объекта.
В примере кода в вашем вопросе mutable может быть неуместным, если значение done_ влияет на внешнее состояние, это зависит от того, что находится в ...;часть.
Mutable предназначен для обозначения определенного атрибута как изменяемого изнутри const
методы.Это его единственная цель.Тщательно подумайте, прежде чем использовать его, потому что ваш код, вероятно, станет чище и читабельнее, если вы измените дизайн, а не будете использовать mutable
.
http://www.highprogrammer.com/alan/rants/mutable.html
Итак, если вышеупомянутое безумие - это не то, для чего нужен mutable, то для чего он нужен?Вот тонкий случай:mutable предназначен для случая, когда объект логически постоянен, но на практике нуждается в изменении.Таких случаев очень мало, и далеко между ними, но они существуют.
Примеры, которые приводит автор, включают кэширование и временные отладочные переменные.
Это полезно в ситуациях, когда у вас есть скрытое внутреннее состояние, такое как кэш.Например:
class HashTable { ... public: string lookup(string key) const { if(key == lastKey) return lastValue; string value = lookupInternal(key); lastKey = key; lastValue = value; return value; } private: mutable string lastKey, lastValue; };
И тогда у вас может быть const HashTable
объект все еще использует свой lookup()
метод, который изменяет внутренний кэш.
Ну, да, именно это он и делает.Я использую его для членов, которые модифицируются методами, которые не логически измените состояние класса - например, для ускорения поиска путем реализации кэша:
class CIniWrapper
{
public:
CIniWrapper(LPCTSTR szIniFile);
// non-const: logically modifies the state of the object
void SetValue(LPCTSTR szName, LPCTSTR szValue);
// const: does not logically change the object
LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;
// ...
private:
// cache, avoids going to disk when a named value is retrieved multiple times
// does not logically change the public interface, so declared mutable
// so that it can be used by the const GetValue() method
mutable std::map<string, string> m_mapNameToValue;
};
Теперь вы должны использовать это с осторожностью - проблемы параллелизма вызывают большую озабоченность, поскольку вызывающий может предположить, что они потокобезопасны, если используют только const
методы.И, конечно же, модифицируя mutable
данные не должны изменять поведение объекта каким-либо существенным образом, что могло бы быть нарушено в приведенном мной примере, если, например, ожидалось, что изменения, записанные на диск, будут немедленно видны приложению.
mutable
существует, как вы предполагаете, для того, чтобы разрешить изменять данные в постоянной в остальном функции.
Намерение состоит в том, что у вас может быть функция, которая "ничего не делает" с внутренним состоянием объекта, и поэтому вы отмечаете функцию const
, но вам, возможно, действительно потребуется изменить состояние некоторых объектов таким образом, чтобы это не повлияло на его правильную функциональность.
Ключевое слово может действовать как подсказка для компилятора - теоретический компилятор мог бы поместить постоянный объект (например, глобальный) в память, которая была помечена как доступная только для чтения.Наличие mutable
намекает, что этого делать не следует.
Вот несколько веских причин объявлять и использовать изменяемые данные:
- Безопасность потока.Объявляя о
mutable boost::mutex
это вполне разумно. - Статистика.Подсчет количества вызовов функции с учетом некоторых или всех ее аргументов.
- Запоминание.Вычисление какого-нибудь дорогостоящего ответа, а затем сохранение его для использования в будущем, а не повторное вычисление.
Mutable используется, когда у вас есть переменная внутри класса, которая используется только внутри этого класса для сигнализации таких вещей, как, например, мьютекс или блокировка.Эта переменная не изменяет поведение класса, но необходима для реализации потокобезопасности самого класса.Таким образом, если бы не "mutable", вы не смогли бы иметь функции "const", потому что эту переменную необходимо будет изменить во всех функциях, которые доступны внешнему миру.Поэтому mutable был введен для того, чтобы сделать переменную-член доступной для записи даже с помощью функции const.
Указанный изменяемый параметр информирует как компилятор, так и читателя о том, что он безопасен и ожидается, что переменная-член может быть изменена внутри функции-члена const .
mutable в основном используется в деталях реализации класса.Пользователю класса не нужно знать об этом, поэтому метод, который, по его мнению, "должен" быть const, может быть.Ваш пример того, что мьютекс может быть изменяемым, является хорошим каноническим примером.
Ваше использование этого не является взломом, хотя, как и многие вещи в C ++, изменяемый может это хак для ленивого программиста, который не хочет возвращаться назад и отмечать что-то, что не должно быть const, как неконстантное.
Используйте "изменяемый", когда для вещей, которые ЛОГИЧЕСКИ не имеют состояния для пользователя (и, следовательно, должны иметь "const" геттеры в API открытого класса), но НЕ являются не имеющими состояния в базовой РЕАЛИЗАЦИИ (код в вашем .cpp).
Случаи, когда я использую его чаще всего, - это отложенная инициализация элементов "простых старых данных" без состояния.А именно, это идеально в узких случаях, когда такие элементы дороги либо для сборки (процессор), либо для переноса (память), и многие пользователи объекта никогда не будут запрашивать их.В этой ситуации вам нужно отложенное построение на серверной части для повышения производительности, поскольку 90% созданных объектов вообще никогда не понадобятся для их создания, но вам все равно нужно представить правильный API без состояния для общего пользования.
Изменяемый изменяет значение const
от побитовой const к логической const для класса.
Это означает, что классы с изменяемыми членами больше не являются побитовыми const и больше не будут отображаться в разделах исполняемого файла, доступных только для чтения.
Кроме того, он изменяет проверку типов, позволяя const
функции-члены для изменения изменяемых членов без использования const_cast
.
class Logical {
mutable int var;
public:
Logical(): var(0) {}
void set(int x) const { var = x; }
};
class Bitwise {
int var;
public:
Bitwise(): var(0) {}
void set(int x) const {
const_cast<Bitwise*>(this)->var = x;
}
};
const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.
int main(void)
{
logical.set(5); // Well defined.
bitwise.set(5); // Undefined.
}
Смотрите другие ответы для получения более подробной информации, но я хотел подчеркнуть, что это не просто для безопасности типов и что это влияет на скомпилированный результат.
В некоторых случаях (например, плохо спроектированные итераторы) классу необходимо сохранять значение count или какое-то другое случайное значение, которое на самом деле не влияет на основное "состояние" класса.Чаще всего я вижу, как используется изменяемый параметр.Без mutable вам пришлось бы пожертвовать всей неизменностью вашего дизайна.
Мне тоже большую часть времени это кажется взломом.Полезно в очень-очень немногих ситуациях.
Классический пример (как упоминалось в других ответах) и единственная ситуация, которую я видел mutable
ключевое слово, используемое до сих пор, предназначено для кэширования результата сложной Get
метод, в котором кэш реализован как элемент данных класса, а не как статическая переменная в методе (по соображениям совместного использования между несколькими функциями или простой чистоты).
В целом, альтернативы использованию mutable
ключевое слово обычно является статической переменной в методе или const_cast
трюк.
Другое подробное объяснение содержится в здесь.
Изменяемый параметр может быть удобен, когда вы переопределяете виртуальную функцию const и хотите изменить переменную-член вашего дочернего класса в этой функции.В большинстве случаев вы не хотели бы изменять интерфейс базового класса, поэтому вам придется использовать собственную изменяемую переменную-член.
Ключевое слово mutable очень полезно при создании заглушек для целей тестирования класса.Вы можете заглушить функцию const и при этом иметь возможность увеличивать (изменять) счетчики или любую другую тестовую функциональность, которую вы добавили в свою заглушку.Это сохраняет интерфейс заглушенного класса нетронутым.
Один из лучших примеров, где мы используем mutable, - это in deep copy.в конструкторе копирования мы отправляем const &obj
в качестве аргумента.Таким образом, созданный новый объект будет иметь постоянный тип.Если мы хотим изменить (в основном мы не будем изменять, в редких случаях мы можем изменить) элементы в этом вновь созданном объекте const, нам нужно объявить его как mutable
.
mutable
класс хранения может использоваться только для нестатических неконстантных данных-членов класса.Изменяемый элемент данных класса может быть изменен, даже если он является частью объекта, который объявлен как const .
class Test
{
public:
Test(): x(1), y(1) {};
mutable int x;
int y;
};
int main()
{
const Test object;
object.x = 123;
//object.y = 123;
/*
* The above line if uncommented, will create compilation error.
*/
cout<< "X:"<< object.x << ", Y:" << object.y;
return 0;
}
Output:-
X:123, Y:1
В приведенном выше примере мы можем изменить значение переменной-члена x
хотя это часть объекта, который объявлен как const .Это происходит потому, что переменная x
объявляется как изменяемый.Но если вы попытаетесь изменить значение переменной-члена y
, компилятор выдаст сообщение об ошибке.
Само ключевое слово 'mutable' на самом деле является зарезервированным ключевым словом. часто оно используется для изменения значения постоянной переменной.Если вы хотите иметь несколько значений constsnt, используйте ключевое слово mutable .
//Prototype
class tag_name{
:
:
mutable var_name;
:
:
};