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


527

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

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

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


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

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

1
Примечание: вот отличный пример использования ключевого слова 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).
alfC

Ответы:


351

Это позволяет дифференцировать побитовый констант и логический констант. Логическая константа - это когда объект не изменяется таким образом, который виден через открытый интерфейс, как ваш пример блокировки. Другим примером может быть класс, который вычисляет значение при первом запросе и кэширует результат.

Поскольку 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

52
'mutable' вообще не влияет на битовую / логическую константность. C ++ является только побитовым const, и ключевое слово 'mutable' может использоваться для исключения членов из этой проверки. Невозможно достичь «логического» const в C ++, кроме как с помощью абстракций (например, SmartPtrs).
Ричард Корден

111
@Richard: ты упускаешь суть. Ключевого слова «логическая константа» не существует, правда, скорее, это концептуальное различие, которое программист делает, чтобы решить, какие элементы следует исключить, сделав изменяемыми, основываясь на понимании того, что составляет логическое наблюдаемое состояние объекта.
Тони Делрой

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

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

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

138

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методах, работающих с частными данными.

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


8
Кроме того, использование const_castдля изменения части constобъекта приводит к неопределенному поведению.
Брайан

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

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

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

75

Ваше использование с boost :: mutex - именно то, для чего предназначено это ключевое слово. Другое использование для внутреннего кэширования результатов для ускорения доступа.

По сути, 'mutable' применяется к любому атрибуту класса, который не влияет на внешне видимое состояние объекта.

В примере кода на ваш вопрос изменяемый может быть неуместным, если значение done_ влияет на внешнее состояние, это зависит от того, что находится в ...; часть.


35

Mutable предназначен для маркировки конкретного атрибута как модифицируемого изнутри constметодов. Это его единственная цель. Тщательно подумайте, прежде чем использовать его, потому что ваш код, вероятно, будет чище и более читабельным, если вы измените дизайн, а не будете его использовать mutable.

http://www.highprogrammer.com/alan/rants/mutable.html

Так что, если вышеупомянутое безумие не то, для чего изменчиво, для чего оно? Вот тонкий случай: переменная предназначена для случая, когда объект является логически постоянным, но на практике это необходимо изменить. Эти случаи редки, но они существуют.

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


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

Использование mutableможет сделать код более читабельным и понятным. В следующем примере значение readможет быть таким, constкак ожидалось. `mutable m_mutex; Контейнер m_container; void add (Item item) {Замок блокировки (m_mutex); m_container.pushback (пункт); } Элемент read () const {Lockguard lock (m_mutex); return m_container.first (); } `
чт. Тилеманн

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

33

Это полезно в ситуациях, когда у вас есть скрытое внутреннее состояние, такое как кеш. Например:

класс HashTable
{
...
общественности:
    поиск строки (строковый ключ) const
    {
        если (ключ == lastKey)
            return lastValue;

        строковое значение = lookupInternal (ключ);

        lastKey = ключ;
        lastValue = значение;

        возвращаемое значение;
    }

частный:
    изменяемая строка lastKey, lastValue;
};

И тогда вы можете заставить const HashTableобъект все еще использовать его lookup()метод, который модифицирует внутренний кеш.


9

mutable существует, если вы сделаете вывод, что можно изменять данные в другой постоянной функции.

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

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

Вот несколько веских причин объявлять и использовать изменяемые данные:

  • Поток безопасности. Объявление mutable boost::mutexвполне разумно.
  • Статистика. Подсчет количества вызовов функции с учетом некоторых или всех ее аргументов.
  • Запоминание. Вычисление некоторого дорогостоящего ответа, а затем сохранение его для использования в будущем вместо повторного вычисления.

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

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

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

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

8

Ну, да, это то, что он делает. Я использую его для членов, которые модифицируются методами, которые не изменяют логически состояние класса, например, для ускорения поиска путем реализации кэша:

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


6

Mutable используется, когда у вас есть переменная внутри класса, которая используется только внутри этого класса для сигнализации таких вещей, как, например, мьютекс или блокировка. Эта переменная не меняет поведение класса, но необходима для обеспечения безопасности потока самого класса. Таким образом, если бы не «изменяемый», вы не смогли бы иметь «константные» функции, потому что эту переменную нужно будет изменить во всех функциях, доступных внешнему миру. Поэтому, mutable был введен для того, чтобы сделать переменную-член доступной для записи даже функцией const.

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


4

mutable в основном используется в деталях реализации класса. Пользователь класса не должен знать об этом, поэтому метод, который он считает «должен» быть постоянным, может быть. Ваш пример того, что mutex должен быть изменяемым, является хорошим каноническим примером.


4

Его использование не является хаком, хотя, как и многие другие вещи в C ++, изменяемый может быть хаком для ленивого программиста, который не хочет идти назад и помечать что-то, что не должно быть константным, как неконстантное.


3

Используйте «mutable», когда для вещей, которые ЛОГИЧЕСКИ не сохраняют состояния для пользователя (и, следовательно, должны иметь «const» геттеры в API-интерфейсах открытого класса), но НЕ являются безлимитными в базовой реализации (код в вашем .cpp).

Случаи, которые я использую чаще всего, - это ленивая инициализация "простых старых данных" без состояния. А именно, он идеален в узких случаях, когда такие элементы дороги либо для сборки (процессор), либо для переноса (память), и многие пользователи объекта никогда не будут запрашивать их. В этой ситуации вы хотите ленивую конструкцию на стороне сервера для повышения производительности, поскольку 90% созданных объектов вообще не нужно будет создавать их, но вам все равно нужно представить правильный API без сохранения состояния для общего пользования.


2

Изменяемое меняет значение 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.
}

См. Другие ответы для более подробной информации, но я хотел бы подчеркнуть, что это не просто для безопасности типов и что это влияет на скомпилированный результат.


1

В некоторых случаях (например, плохо спроектированные итераторы) класс должен хранить счетчик или другое случайное значение, которое не влияет на основное «состояние» класса. Это чаще всего, где я вижу изменчивое использование. Без изменчивости вы были бы вынуждены пожертвовать всем постоянством вашего дизайна.

Мне кажется, что большую часть времени я чувствую это как хак. Полезно в очень немногих ситуациях.


1

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

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

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


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

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

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

1

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


1

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


0

Один из лучших примеров, где мы используем 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, компилятор выдаст ошибку.


-1

Само ключевое слово 'mutable' на самом деле является зарезервированным ключевым словом. Часто оно используется для изменения значения постоянной переменной. Если вы хотите иметь несколько значений constsnt, используйте ключевое слово mutable.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.