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

447

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

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

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

  • 2
    Вопрос, однако, если вы ничего не модифицируете, зачем вам сначала использовать мьютекс? Я просто хочу понять это.
  • 0
    @Misgevolution вы что-то модифицируете, вы просто контролируете, кто / как может сделать модификацию через const. Действительно наивный пример, представьте, что если я даю неконстантные маркеры друзьям, враги получают константные маркеры. Друзья могут изменить, враги не могут.
Показать ещё 1 комментарий
Теги:
mutable
keyword

18 ответов

301
Лучший ответ

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

Так как С++ 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
  • 48
    'mutable' вообще не влияет на битовую / логическую константность. C ++ является только побитовым const, и ключевое слово 'mutable' может использоваться для исключения членов из этой проверки. Невозможно достичь «логического» const в C ++, кроме как с помощью абстракций (например, SmartPtrs).
  • 105
    @Richard: ты упускаешь суть. Ключевого слова «логическая константа» не существует, правда, скорее, это концептуальное различие, которое программист делает, чтобы решить, какие элементы следует исключить, сделав его изменчивым, основываясь на понимании того, что составляет логическое наблюдаемое состояние объекта.
Показать ещё 12 комментариев
123

Ключевое слово mutable - это способ прошивки завесы const, которую вы накладываете на свои объекты. Если у вас есть ссылка на 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 с указателем или ссылкой const позволяет неограниченную запись и метод, вызывающий доступ к видимым членам. Напротив, mutable требует, чтобы дизайнер API выполнял мелкозернистый контроль над исключениями const, и обычно эти исключения скрыты в методах const, работающих с частными данными.

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

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

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

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

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

  • 8
    +1 за упоминание о кешировании и его использовании с объектами синхронизации, т.е. мьютексом в данном случае. Также критические разделы и семафоры.
34

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

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

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

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

  • 2
    Я думаю, что эта ссылка дает лучший пример сценария, в котором изменчивость полезна. Почти кажется, что они используются исключительно для отладки. (за правильное использование)
  • 0
    Использование 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 (); } `
28

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

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

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

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

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

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

  • Безопасность резьбы. Объявление a mutable boost::mutex вполне разумно.
  • Статистика. Подсчет количества вызовов функции, учитывая некоторые или все его аргументы.
  • запоминание. Вычисляя некоторый дорогой ответ, а затем сохраняя его для будущей ссылки, а не повторно вычисляя его.
  • 2
    Хороший ответ, за исключением комментариев относительно изменчивости, являющейся «подсказкой». Это создает впечатление, что изменяемый элемент иногда не будет изменяемым, если компилятор поместит объект в ПЗУ. Поведение изменчивых хорошо определено.
  • 2
    Помимо помещения объекта const в постоянную память, компилятор также может решить, например, оптимизировать вызовы const fucntion вне цикла. Изменчивый счетчик статистики в другой константной функции по-прежнему допускает такую оптимизацию (и учитывает только один вызов) вместо предотвращения оптимизации только для подсчета большего количества вызовов.
Показать ещё 2 комментария
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;
};

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

5

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

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

4

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

4

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

3

Используйте "mutable", когда для вещей, которые LOGICALLY не имеют статуса для пользователя (и, следовательно, должны иметь "const" getters в API открытого класса), но не являются апатридами в базовом ОСУЩЕСТВЛЕНИИ (код в вашем .cpp).

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

1

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

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

1

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

1

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

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

Еще одно подробное объяснение в здесь.

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

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

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

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

0

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

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню