Некоторое время назад я наткнулся на код, в котором переменная-член класса помечена ключевым словом mutable. Насколько я понимаю, он просто позволяет вам изменять переменную в методе const:
class Foo
{
private:
mutable bool done_;
public:
void doSomething() const { ...; done_ = true; }
};
Это единственное использование этого ключевого слова или это нечто большее, чем кажется на первый взгляд? С тех пор я использовал эту технику в классе, отмечая boost::mutex как изменяемый, что позволяет функциям const блокировать его из соображений безопасности потоков, но, честно говоря, это похоже на взлом.
@Misgevolution вы что-то модифицируете, вы просто контролируете, кто / как может делать модификацию через const. Действительно наивный пример, представьте, что если бы я давал друзьям только неконстантные дескрипторы, враги получат константные дескрипторы. Друзья могут видоизменяться, а враги - нет.
Примечание: вот отличный пример использования ключевого слова mutable: stackoverflow.com/questions/15999123/…
Я бы хотел, чтобы его можно было использовать для переопределения const (типов), поэтому мне не нужно этого делать: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;, если я хочу const по умолчанию, то есть mutable A a; (явно изменяемый) и A a; (неявный const).





Ваше использование с boost :: mutex - это именно то, для чего предназначено это ключевое слово. Другое использование - внутреннее кэширование результатов для ускорения доступа.
По сути, «изменчивый» применяется к любому атрибуту класса, который не влияет на внешне видимое состояние объекта.
В примере кода вашего вопроса mutable может быть неприемлемым, если значение done_ влияет на внешнее состояние, это зависит от того, что находится в ...; часть.
Mutable предназначен для обозначения определенного атрибута как изменяемого из методов const. Это его единственная цель. Тщательно подумайте, прежде чем использовать его, потому что ваш код, вероятно, будет чище и читабельнее, если вы измените дизайн, а не используете mutable.
http://www.highprogrammer.com/alan/rants/mutable.html
So if the above madness isn't what mutable is for, what is it for? Here's the subtle case: mutable is for the case where an object is logically constant, but in practice needs to change. These cases are few and far between, but they exist.
Примеры, которые приводит автор, включают кеширование и временные отладочные переменные.
Я думаю, что эта ссылка является лучшим примером сценария, в котором полезны изменяемые параметры. Похоже, что они используются исключительно для отладки. (за правильное использование)
Использование mutable может сделать код более читаемым и чище. В следующем примере read может быть const, как и ожидалось. `mutable m_mutex; Контейнер m_container; void add (Item item) {Lockguard lock (m_mutex); m_container.pushback (элемент); } Элемент read () const {Lockguard lock (m_mutex); вернуть m_container.first (); } `
Есть один чрезвычайно популярный вариант использования: счетчик ссылок.
В некоторых случаях (например, плохо спроектированные итераторы) классу необходимо вести счетчик или другое случайное значение, которое на самом деле не влияет на основное «состояние» класса. Чаще всего я вижу, что используется изменяемый. Без 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(), который изменяет внутренний кеш.
Это позволяет различать поразрядную константу и логическую константу. Логическая константа - это когда объект не изменяется так, как это видно через общедоступный интерфейс, как в вашем примере блокировки. Другой пример - класс, который вычисляет значение при первом запросе и кэширует результат.
Поскольку 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' вообще не влияет на поразрядную / логическую константу. C++ - это побитовая константа Только, и ключевое слово mutable может использоваться для исключения членов из этой проверки. Невозможно достичь «логической» константы в C++, кроме как с помощью абстракций (например, SmartPtrs).
@ Ричард: ты упускаешь суть. Не существует ключевого слова «логическая константа», правда, это скорее концептуальная дифференциация, которую программист делает, чтобы решить, какие члены должны быть исключены, сделав их изменяемыми, на основе понимания того, что составляет логическое наблюдаемое состояние объекта.
Я думаю, что комментарий @Ricарда ценен тем, что он помогает более детально прояснить объяснение KeithB. По крайней мере, мне это помогло :)
И KeithB, и @RichardCorden верны, в зависимости от точки зрения. Если бы термины, используемые KeithB, были определены более строго, не было бы двусмысленности (и, вероятно, разногласий); но двусмысленность не влияет на ценность ответа KeithB, на мой взгляд - это ценный ответ и, как мне кажется, правильный.
можем ли мы изменить изменяемые элементы данных константного объекта?
@ajay Да, в этом весь смысл маркировать переменную-член как изменяемую, чтобы ее можно было изменять в константных объектах.
Зачем нужны изменяемые лямбды? Разве не хватит захвата переменной по ссылке?
@Giorgio: разница в том, что модифицированный x внутри лямбда остается внутри лямбда, т.е. лямбда-функция может изменять только свою собственную копию x. Снаружи изменений не видно, оригинальный x без изменений. Учтите, что лямбды реализованы как классы функторов; захваченные переменные соответствуют переменным-членам.
@phresnel: Хорошо, тогда я не понимаю, почему захваченные переменные (переменные-члены, которые являются копиями переменных из контекста) по умолчанию являются константными. Есть ли для этого особая причина?
«Логическая константа - это когда объект не изменяется так, как это видно через общедоступный интерфейс, как в вашем примере блокировки.»: В каком смысле использование мьютекса не меняет наблюдаемое поведение объекта? В следующий раз, когда метод блокировки будет вызван другим потоком, метод остановится и будет ждать, пока поток, удерживающий блокировку, не выйдет из метода блокировки. Мне это кажется заметным изменением поведения.
@Giorgio: Может, мой новый вопрос / ответ немного поможет: stackoverflow.com/questions/16944894/…
@ Джорджио, допустим, у меня есть тостер, и я ставлю его в комнату. Когда люди хотят воспользоваться тостером, вы входите в комнату и запираете дверь. Когда вы закончите, вы открываете дверь и выходите из комнаты (дверь можно запереть / отпереть только изнутри). Скажем, я получаю доступ в комнату, запираю дверь и использую тостер. Вы хотите использовать тостер. Но дверь заперта. Изменилось ли наблюдаемое поведение тостера? Нет. Тостер по-прежнему вмещает только два куска хлеба. По-прежнему превращает сырой хлеб в тосты. Доступ к тостеру не является частью его поведения.
@iheanyi Отличная аналогия, остроумно поставленная. И, как ни странно, поскольку на днях, когда мне пришлось сделать перерыв от бесплодной отладки - которая, как позже выяснится, связана с многопоточностью, Не меньше - я подумал, что расслаблюсь с поджаренными картофельными лепешками (да, это то, что я говоря о). Вместо этого я взорвал автоматический выключатель. Учитывая, что предшествовало этой попытке, я не понимаю, почему я был слегка удивлен! Думаю, мой тостер тоже не был потокобезопасным.
зачем использовать mutable, когда удаление const обеспечивает изменчивость? Разве переопределение constness не плохо?
@Mushy с использованием ключевого слова const подразумевает постоянство с точки зрения пользователя класса. Если я инициализирую c1 и c2 как const MyCls c1(); const MyCls c2 = c1;, то в любой части кода, начиная с этого момента, c1 == c2 всегда должен быть истинным. Поскольку равенство определяется самим классом, он может выбирать, какие члены использовать для проверки равенства. Следовательно (например), если он решает использовать только общедоступные члены для этой проверки, тогда он может сделать частные переменные столь же изменяемыми, поскольку это не изменяет константность объекта, а также может помочь объекту в его внутренней работе.
mutable в основном используется в деталях реализации класса. Пользователю класса не нужно знать об этом, поэтому метод, который, по его мнению, «должен» быть const, может быть. Ваш пример с изменяемым мьютексом - хороший канонический пример.
Ну да, вот что он делает. Я использую его для членов, которые изменяются методами, которые не изменяют логически состояние класса - например, для ускорения поиска путем реализации кеша:
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 не должно существенно изменить поведение объекта, что могло бы быть нарушено приведенным мной примером, если, например, ожидалось, что изменения, записанные на диск, будут немедленно видимы для пользователя. приложение.
Его использование не является хакерским, хотя, как и многие другие вещи в C++, изменяемый может - это хитрость для ленивого программиста, который не хочет возвращаться полностью и отмечать то, что не должно быть константным, как неконстантное.
mutable действительно существует, как вы предполагаете, чтобы позволить изменять данные в другой постоянной функции.
Предполагается, что у вас может быть функция, которая «ничего не делает» для внутреннего состояния объекта, и поэтому вы помечаете функцию const, но вам действительно может потребоваться изменить состояние некоторых объектов таким образом, чтобы это не влияло на его правильный функционал.
Ключевое слово может действовать как подсказка для компилятора - теоретический компилятор может поместить в память постоянный объект (например, глобальный), который был помечен как доступный только для чтения. Наличие mutable намекает, что этого делать не следует.
Вот несколько веских причин для объявления и использования изменяемых данных:
mutable boost::mutex совершенно разумно.Хороший ответ, за исключением комментария о том, что изменяемый является «подсказкой». Это создает впечатление, что изменяемый член иногда не будет изменяемым, если компилятор поместил объект в ПЗУ. Поведение изменчивого хорошо определено.
Помимо помещения константного объекта в постоянную память, компилятор может также решить оптимизировать, например, вызовы константных функций вне цикла. Изменяемый счетчик статистики в функции const по-прежнему разрешает такую оптимизацию (и учитывает только один вызов) вместо того, чтобы предотвращать оптимизацию только ради подсчета большего количества вызовов.
@HagenvonEitzen - Я почти уверен, что это неверно. Компилятор не может выводить функции из цикла, если он не может доказать отсутствие побочных эффектов. Это доказательство обычно включает в себя фактическую проверку реализации функции (часто после того, как она встроена) и не полагается на const (и такая проверка будет успешной или неудачной независимо от const или mutable). Простого объявления функции const недостаточно: функция const может иметь побочные эффекты, такие как изменение глобальной переменной или чего-то, переданного в функцию, поэтому это не является полезной гарантией для этого доказательства.
Теперь у некоторых компиляторов есть специальные расширения, такие как _attribute __ ((const)) и __attribute __ ((чистый)), которые _doиметь такие эффекты в gcc, но это лишь косвенно связано с ключевым словом const в C++.
Ключевое слово 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, работающих с частными данными.
(N.B. Я несколько раз ссылаюсь на данные и метод видимость. Я говорю о членах, помеченных как открытые, частные или защищенные, что представляет собой совершенно другой тип защиты объекта, обсуждаемый здесь.)
Кроме того, использование const_cast для изменения части объекта const приводит к неопределенному поведению.
Я не согласен с потому что он заставляет клиент API разрушать постоянную защиту объектов. Если бы вы использовали const_cast для реализации мутации переменных-членов в методе const, вы бы не просили клиента выполнить приведение - вы бы сделали это в рамках метода с помощью const_casting this. По сути, он позволяет вам обходить константу для произвольных членов в конкретный сайт звонка, в то время как mutable позволяет вам удалять константу на конкретный член на всех сайтах вызова. Последнее обычно является тем, что вам нужно для типичного использования (кеширование, статистика), но иногда const_cast соответствует шаблону.
Шаблон const_cast лучше подходит в некоторых случаях, например, когда вы хотите временно изменить член, а затем восстановить его (почти как boost::mutex). Этот метод является логически константным, поскольку конечное состояние такое же, как и исходное, но вы хотите внести это временное изменение. const_cast может быть здесь полезен, потому что он позволяет вам отбрасывать константу именно в этом методе, если мутация будет отменена, но mutable не будет таким подходящим, поскольку он удалит защиту const из методов все, которые не обязательно все следуют шаблон "сделать, отменить".
Возможное размещение объекта const определенный в постоянной памяти (в более общем смысле, в памяти отмечен только для чтения) и соответствующий стандартный язык, который позволяет это, делает const_cast возможной бомбой замедленного действия. mutable не имеет такой проблемы, поскольку такие объекты не могут быть помещены в постоянную память.
Классический пример (как упоминалось в других ответах) и единственная ситуация, в которой я видел ключевое слово mutable, используемое до сих пор, - это кеширование результата сложного метода Get, где кеш реализован как член данных класса, а не как статическая переменная в методе (по причинам разделения между несколькими функциями или простой чистоты).
В общем, альтернативой использованию ключевого слова mutable обычно является статическая переменная в методе или трюк const_cast.
Другое подробное объяснение находится в здесь.
Я никогда не слышал об использовании статических членов в качестве общей альтернативы изменяемым членам. И const_cast предназначен только для случаев, когда вы знать (или были гарантированы), что что-то не будет изменено (например, при вмешательстве в библиотеки C) или когда вы знать не объявили const. То есть изменение константной переменной const приводит к неопределенному поведению.
@phresnel Под «статическими переменными» я имел в виду статические автоматические переменные в методе (которые остаются во время вызовов). А const_cast можно использовать для изменения члена класса в методе const, о чем я уже говорил ...
Мне это было непонятно, поскольку вы написали «в целом» :) Что касается модификации через const_cast, как сказано, это разрешено только тогда, когда объект не был объявлен как const. Например. const Frob f; f.something(); с void something() const { const_cast<int&>(m_foo) = 2; приводит к неопределенному поведению.
Mutable используется, когда у вас есть переменная внутри класса, которая используется только внутри этого класса, чтобы сигнализировать о таких вещах, как, например, мьютекс или блокировка. Эта переменная не меняет поведения класса, но необходима для реализации потоковой безопасности самого класса. Таким образом, без «изменяемого» вы не сможете иметь «константные» функции, потому что эту переменную нужно будет изменить во всех функциях, доступных внешнему миру. Поэтому mutable был введен для того, чтобы сделать переменную-член доступной для записи даже для константной функции.
The mutable specified informs both the compiler and the reader that it is safe and expected that a member variable may be modified within a const member function.
Используйте "изменяемый", когда для вещей, которые ЛОГИЧЕСКИ не имеют состояния для пользователя (и, следовательно, должны иметь "константные" геттеры в API общедоступных классов), но НЕ не имеют состояния в базовой РЕАЛИЗАЦИИ (код в вашем .cpp).
Случаи, которые я использую чаще всего, - это ленивая инициализация членов "простых старых данных" без состояния. А именно, он идеален в узких случаях, когда создание таких элементов (процессор) или переноска (память) требует больших затрат, и многие пользователи объекта никогда не будут их запрашивать. В этой ситуации вам нужно ленивое построение на серверной части для повышения производительности, поскольку 90% построенных объектов вообще не нуждаются в их построении, но вам все равно необходимо предоставить правильный API без сохранения состояния для публичного использования.
Изменяемый может быть удобен, когда вы переопределяете виртуальную функцию const и хотите изменить переменную члена дочернего класса в этой функции. В большинстве случаев вы не захотите изменять интерфейс базового класса, поэтому вам придется использовать собственные изменяемые переменные-члены.
Само ключевое слово mutable на самом деле является зарезервированным. Часто оно используется для изменения значения постоянной переменной. Если вы хотите иметь несколько значений константы, используйте ключевое слово mutable.
//Prototype
class tag_name{
:
:
mutable var_name;
:
:
};
Ключевое слово mutable очень полезно при создании заглушек для целей тестирования классов. Вы можете заглушить константную функцию и по-прежнему иметь возможность увеличивать (изменяемые) счетчики или любую другую тестовую функцию, которую вы добавили в свою заглушку. Это сохраняет неизменным интерфейс заглушенного класса.
Mutable изменяет значение 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.
}
Дополнительные сведения см. В других ответах, но я хотел подчеркнуть, что это не только для обеспечения безопасности типов и влияет на скомпилированный результат.
Один из лучших примеров использования mutable - это глубокая копия. в конструкторе копирования мы отправляем 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, компилятор выдаст ошибку.
Однако возникает вопрос: если вы ничего не изменяете, зачем вам в первую очередь использовать мьютекс? Я просто хочу это понять.