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

StackOverflow https://stackoverflow.com/questions/105014

  •  01-07-2019
  •  | 
  •  

Вопрос

Некоторое время назад я наткнулся на некоторый код, который помечал переменную-член класса символом 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;
                :
                :
               };   
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top