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

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

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

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

Ответы [ 18 ]

331 голосов
/ 20 сентября 2008

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

Поскольку 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
125 голосов
/ 05 марта 2010

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

С вашей const ссылкой или указателем вы обязаны:

  • доступ только для чтения для всех видимых элементов данных
  • разрешение на вызов только тех методов, которые помечены как const.

Исключение mutable делает его таким образом, что теперь вы можете записывать или устанавливать элементы данных, помеченные mutable. Это единственное внешне видимое отличие.

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

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

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

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

75 голосов
/ 20 сентября 2008

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

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

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

35 голосов
/ 20 сентября 2008

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

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

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

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

31 голосов
/ 20 сентября 2008

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

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(), который изменяет внутренний кеш.

8 голосов
/ 20 сентября 2008

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

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

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

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

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

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

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 голосов
/ 28 июня 2013

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

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

4 голосов
/ 20 сентября 2008

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

4 голосов
/ 20 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...