Имеет ли ключевое слово mutable какую-либо цель, кроме разрешения изменения переменной с помощью константной функции?

Некоторое время назад я наткнулся на код, в котором переменная-член класса помечена ключевым словом mutable. Насколько я понимаю, он просто позволяет вам изменять переменную в методе const:

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

Это единственное использование этого ключевого слова или это нечто большее, чем кажется на первый взгляд? С тех пор я использовал эту технику в классе, отмечая boost::mutex как изменяемый, что позволяет функциям const блокировать его из соображений безопасности потоков, но, честно говоря, это похоже на взлом.

Однако возникает вопрос: если вы ничего не изменяете, зачем вам в первую очередь использовать мьютекс? Я просто хочу это понять.

Misgevolution 19.08.2016 07:39

@Misgevolution вы что-то модифицируете, вы просто контролируете, кто / как может делать модификацию через const. Действительно наивный пример, представьте, что если бы я давал друзьям только неконстантные дескрипторы, враги получат константные дескрипторы. Друзья могут видоизменяться, а враги - нет.

iheanyi 23.05.2017 00:35

Примечание: вот отличный пример использования ключевого слова mutable: stackoverflow.com/questions/15999123/…

Gabriel Staples 05.08.2017 20:19

Я бы хотел, чтобы его можно было использовать для переопределения const (типов), поэтому мне не нужно этого делать: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;, если я хочу const по умолчанию, то есть mutable A a; (явно изменяемый) и A a; (неявный const).

alfC 19.10.2019 01:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
548
4
232 243
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

Ваше использование с 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.

Примеры, которые приводит автор, включают кеширование и временные отладочные переменные.

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

enthusiasticgeek 25.07.2012 18:08

Использование 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 (); } `

Th. Thielemann 07.01.2019 19:21

Есть один чрезвычайно популярный вариант использования: счетчик ссылок.

Seva Alekseyev 04.05.2020 23:10

В некоторых случаях (например, плохо спроектированные итераторы) классу необходимо вести счетчик или другое случайное значение, которое на самом деле не влияет на основное «состояние» класса. Чаще всего я вижу, что используется изменяемый. Без 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).

Richard Corden 22.09.2008 12:13

@ Ричард: ты упускаешь суть. Не существует ключевого слова «логическая константа», правда, это скорее концептуальная дифференциация, которую программист делает, чтобы решить, какие члены должны быть исключены, сделав их изменяемыми, на основе понимания того, что составляет логическое наблюдаемое состояние объекта.

Tony Delroy 29.07.2011 06:11

Я думаю, что комментарий @Ricарда ценен тем, что он помогает более детально прояснить объяснение KeithB. По крайней мере, мне это помогло :)

scorpiodawg 20.09.2011 00:48

И KeithB, и @RichardCorden верны, в зависимости от точки зрения. Если бы термины, используемые KeithB, были определены более строго, не было бы двусмысленности (и, вероятно, разногласий); но двусмысленность не влияет на ценность ответа KeithB, на мой взгляд - это ценный ответ и, как мне кажется, правильный.

Dan Nissenbaum 29.05.2012 09:09

можем ли мы изменить изменяемые элементы данных константного объекта?

null 06.03.2013 17:27

@ajay Да, в этом весь смысл маркировать переменную-член как изменяемую, чтобы ее можно было изменять в константных объектах.

KeithB 06.03.2013 18:48

Зачем нужны изменяемые лямбды? Разве не хватит захвата переменной по ссылке?

Giorgio 24.04.2013 15:02

@Giorgio: разница в том, что модифицированный x внутри лямбда остается внутри лямбда, т.е. лямбда-функция может изменять только свою собственную копию x. Снаружи изменений не видно, оригинальный x без изменений. Учтите, что лямбды реализованы как классы функторов; захваченные переменные соответствуют переменным-членам.

Sebastian Mach 05.06.2013 19:28

@phresnel: Хорошо, тогда я не понимаю, почему захваченные переменные (переменные-члены, которые являются копиями переменных из контекста) по умолчанию являются константными. Есть ли для этого особая причина?

Giorgio 05.06.2013 19:37

«Логическая константа - это когда объект не изменяется так, как это видно через общедоступный интерфейс, как в вашем примере блокировки.»: В каком смысле использование мьютекса не меняет наблюдаемое поведение объекта? В следующий раз, когда метод блокировки будет вызван другим потоком, метод остановится и будет ждать, пока поток, удерживающий блокировку, не выйдет из метода блокировки. Мне это кажется заметным изменением поведения.

Giorgio 05.06.2013 19:46

@Giorgio: Может, мой новый вопрос / ответ немного поможет: stackoverflow.com/questions/16944894/…

Sebastian Mach 05.06.2013 20:16

@ Джорджио, допустим, у меня есть тостер, и я ставлю его в комнату. Когда люди хотят воспользоваться тостером, вы входите в комнату и запираете дверь. Когда вы закончите, вы открываете дверь и выходите из комнаты (дверь можно запереть / отпереть только изнутри). Скажем, я получаю доступ в комнату, запираю дверь и использую тостер. Вы хотите использовать тостер. Но дверь заперта. Изменилось ли наблюдаемое поведение тостера? Нет. Тостер по-прежнему вмещает только два куска хлеба. По-прежнему превращает сырой хлеб в тосты. Доступ к тостеру не является частью его поведения.

iheanyi 05.06.2014 22:58

@iheanyi Отличная аналогия, остроумно поставленная. И, как ни странно, поскольку на днях, когда мне пришлось сделать перерыв от бесплодной отладки - которая, как позже выяснится, связана с многопоточностью, Не меньше - я подумал, что расслаблюсь с поджаренными картофельными лепешками (да, это то, что я говоря о). Вместо этого я взорвал автоматический выключатель. Учитывая, что предшествовало этой попытке, я не понимаю, почему я был слегка удивлен! Думаю, мой тостер тоже не был потокобезопасным.

underscore_d 16.02.2016 00:01

зачем использовать mutable, когда удаление const обеспечивает изменчивость? Разве переопределение constness не плохо?

Mushy 18.10.2017 20:32

@Mushy с использованием ключевого слова const подразумевает постоянство с точки зрения пользователя класса. Если я инициализирую c1 и c2 как const MyCls c1(); const MyCls c2 = c1;, то в любой части кода, начиная с этого момента, c1 == c2 всегда должен быть истинным. Поскольку равенство определяется самим классом, он может выбирать, какие члены использовать для проверки равенства. Следовательно (например), если он решает использовать только общедоступные члены для этой проверки, тогда он может сделать частные переменные столь же изменяемыми, поскольку это не изменяет константность объекта, а также может помочь объекту в его внутренней работе.

Sourav Kannantha B 14.02.2021 17:25

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 совершенно разумно.
  • Статистика. Подсчет количества вызовов функции с учетом некоторых или всех ее аргументов.
  • Воспоминание. Вычисление дорогостоящего ответа с последующим сохранением его для использования в будущем, а не повторное вычисление.

Хороший ответ, за исключением комментария о том, что изменяемый является «подсказкой». Это создает впечатление, что изменяемый член иногда не будет изменяемым, если компилятор поместил объект в ПЗУ. Поведение изменчивого хорошо определено.

Richard Corden 22.09.2008 12:17

Помимо помещения константного объекта в постоянную память, компилятор может также решить оптимизировать, например, вызовы константных функций вне цикла. Изменяемый счетчик статистики в функции const по-прежнему разрешает такую ​​оптимизацию (и учитывает только один вызов) вместо того, чтобы предотвращать оптимизацию только ради подсчета большего количества вызовов.

Hagen von Eitzen 19.08.2014 16:31

@HagenvonEitzen - Я почти уверен, что это неверно. Компилятор не может выводить функции из цикла, если он не может доказать отсутствие побочных эффектов. Это доказательство обычно включает в себя фактическую проверку реализации функции (часто после того, как она встроена) и не полагается на const (и такая проверка будет успешной или неудачной независимо от const или mutable). Простого объявления функции const недостаточно: функция const может иметь побочные эффекты, такие как изменение глобальной переменной или чего-то, переданного в функцию, поэтому это не является полезной гарантией для этого доказательства.

BeeOnRope 19.06.2017 01:28

Теперь у некоторых компиляторов есть специальные расширения, такие как _attribute __ ((const)) и __attribute __ ((чистый)), которые _doиметь такие эффекты в gcc, но это лишь косвенно связано с ключевым словом const в C++.

BeeOnRope 19.06.2017 01:32

Ключевое слово 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 приводит к неопределенному поведению.

Brian Bi 06.04.2015 03:25

Я не согласен с потому что он заставляет клиент API разрушать постоянную защиту объектов. Если бы вы использовали const_cast для реализации мутации переменных-членов в методе const, вы бы не просили клиента выполнить приведение - вы бы сделали это в рамках метода с помощью const_casting this. По сути, он позволяет вам обходить константу для произвольных членов в конкретный сайт звонка, в то время как mutable позволяет вам удалять константу на конкретный член на всех сайтах вызова. Последнее обычно является тем, что вам нужно для типичного использования (кеширование, статистика), но иногда const_cast соответствует шаблону.

BeeOnRope 19.06.2017 01:39

Шаблон const_cast лучше подходит в некоторых случаях, например, когда вы хотите временно изменить член, а затем восстановить его (почти как boost::mutex). Этот метод является логически константным, поскольку конечное состояние такое же, как и исходное, но вы хотите внести это временное изменение. const_cast может быть здесь полезен, потому что он позволяет вам отбрасывать константу именно в этом методе, если мутация будет отменена, но mutable не будет таким подходящим, поскольку он удалит защиту const из методов все, которые не обязательно все следуют шаблон "сделать, отменить".

BeeOnRope 19.06.2017 01:48

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

BeeOnRope 19.06.2017 01:54

Классический пример (как упоминалось в других ответах) и единственная ситуация, в которой я видел ключевое слово mutable, используемое до сих пор, - это кеширование результата сложного метода Get, где кеш реализован как член данных класса, а не как статическая переменная в методе (по причинам разделения между несколькими функциями или простой чистоты).

В общем, альтернативой использованию ключевого слова mutable обычно является статическая переменная в методе или трюк const_cast.

Другое подробное объяснение находится в здесь.

Я никогда не слышал об использовании статических членов в качестве общей альтернативы изменяемым членам. И const_cast предназначен только для случаев, когда вы знать (или были гарантированы), что что-то не будет изменено (например, при вмешательстве в библиотеки C) или когда вы знать не объявили const. То есть изменение константной переменной const приводит к неопределенному поведению.

Sebastian Mach 19.11.2013 15:56

@phresnel Под «статическими переменными» я имел в виду статические автоматические переменные в методе (которые остаются во время вызовов). А const_cast можно использовать для изменения члена класса в методе const, о чем я уже говорил ...

Daniel Hershcovich 21.11.2013 14:19

Мне это было непонятно, поскольку вы написали «в целом» :) Что касается модификации через const_cast, как сказано, это разрешено только тогда, когда объект не был объявлен как const. Например. const Frob f; f.something(); с void something() const { const_cast<int&>(m_foo) = 2; приводит к неопределенному поведению.

Sebastian Mach 21.11.2013 14:41

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, компилятор выдаст ошибку.

Другие вопросы по теме