Чи має ключове слово "змінне" якусь іншу мету, крім того, щоб дозволити зміну змінної функцією const?


527

Нещодавно я натрапив на якийсь код, який позначав змінну члена класу з mutableключовим словом. Наскільки я бачу, це просто дозволяє змінювати змінну в constметоді:

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

Це єдине використання цього ключового слова чи його більше, ніж очі? З цього часу я використовував цю методику на уроці, позначаючи boost::mutexяк змінний, що дозволяє constфункціям блокувати її з міркувань безпеки потоку, але, чесно кажучи, це відчувається як би злом.


2
Питання, однак, якщо ви нічого не змінюєте, навіщо вам в першу чергу потрібно використовувати мютекс? Я просто хочу це зрозуміти.
Misgevolution

@Misgevolution ви щось модифікуєте, ви просто контролюєте, хто / як може зробити модифікацію через const. Дійсно наївний приклад, уявіть собі, якщо я даю лише неручні ручки друзям, вороги отримують обробку 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

Це дозволяє диференціювати порозрядне 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

52
"mutable" взагалі не впливає на побітну / логічну стійкість. C ++ - це лише побітовий const, а ключове слово 'mutable' може використовуватися для виключення членів цієї перевірки Неможливо досягти "логічного" const в C ++, крім за допомогою абстракцій (наприклад, SmartPtrs).
Річард Корден

111
@Richard: ти пропускаєш суть. Немає ключового слова "логічний const", правда, скоріше, це концептуальна диференціація, яку програміст вирішує, яких членів слід виключити, зробивши змінними, грунтуючись на розумінні того, що являє собою логічний стан спостереження об'єкта.
Тоні Делрой

6
@ajay Так, у цьому полягає вся суть позначення змінної члена як змінної, щоб дозволити її змінювати в const-об'єктах.
KeithB

6
Для чого потрібно змінювати ламбдаз? Чи не буде достатньо захопити змінну за посиланням?
Джорджіо

11
@ Giorgio: Різниця полягає в тому, що модифікована xламбда залишається в межах лямбда, тобто функція лямбда може змінювати лише свою власну копію x. Зміна не видно зовні, оригінал xвсе ще незмінний. Вважайте, що лямбди реалізуються як заняття з функторів; захоплені змінні відповідають змінним-членам.
Себастьян Мах

138

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 я маю на увазі до даних і метод видимості кілька разів. Я говорю про членів , позначених як суспільного проти приватного або захищаються який є зовсім інший тип захисту об'єкта обговорюється тут .)


8
Плюс, використання const_castмодифікувати частину constоб'єкта призводить до невизначеної поведінки.
Брайан

Я не згоден з тим, що він змушує клієнта API знищити захист const об'єктів . Якщо ви використовували const_castдля впровадження мутації змінних членів у constметоді, ви б не просили клієнта виконувати кастинг - ви зробили б це в методі, використовуючи const_casting this. В основному це дозволяє вам обійти constness для довільних членів на певному сайті виклику , тоді як mutableдавайте вилучимо const на певному члені на всіх сайтах викликів. Останнє зазвичай є тим, що ви хочете для типового використання (кешування, статистика), але іноді const_cast підходить до шаблону.
BeeOnRope

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

2
Можливе розміщення об'єкта, визначеного const, у пам'яті лише для читання (загалом, в пам'яті, позначеній лише для читання) та пов'язаній із нею стандартній мові, що дозволяє це const_castзробити можливою тимчасовою бомбою. mutableне має такої проблеми, оскільки такі об'єкти не можна було розмістити в пам'яті лише для читання.
BeeOnRope

75

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

В основному, "mutable" застосовується до будь-якого атрибуту класу, який не впливає на зовнішній видимий стан об'єкта.

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


35

Змінник призначений для позначення конкретного атрибуту як модифікованого в межах constметодів. Це єдина його мета. Подумайте, перш ніж використовувати його, тому що ваш код, ймовірно, буде більш чистим і читабельним, якщо ви зміните дизайн, а не використовувати mutable.

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

Тож якщо вищезгадане божевілля - це не те, що таке змінне, для чого це? Ось тонкий випадок: змінний - це випадок, коли об’єкт логічно постійний, але на практиці потрібно змінити. Таких випадків небагато і між ними, але вони існують.

Приклади, які автор наводить, включають кешування та тимчасові налагоджувальні змінні.


2
Я думаю, що це посилання дає найкращий приклад сценарію, коли мутабелі є корисними. Він майже не виглядає, що вони використовуються виключно для налагодження. (за правильного використання)
entuziegeek

Використання mutableможе зробити код більш зрозумілим та чистішим. У наступному прикладі readможна так, constяк очікувалося. `змінний m_mutex; Контейнер m_container; void add (Пункт пункту) {Блокування блокування (m_mutex); m_container.pushback (пункт); } Елемент read () const {замок блокування блокування (m_mutex); повернути m_container.first (); } `
Чт. Тілеман

Є один надзвичайно популярний випадок використання: кількість рахунків.
Сева Алексєєва

33

Це корисно в ситуаціях, коли у вас є прихований внутрішній стан, наприклад кеш. Наприклад:

клас HashTable
{
...
загальнодоступний:
    пошук рядка (строковий ключ) const
    {
        if (ключ == lastKey)
            повернути lastValue;

        значення рядка = lookupInternal (ключ);

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

        повернення вартості;
    }

приватний:
    змінна рядок lastKey, lastValue;
};

І тоді ви можете мати const HashTableоб’єкт все-таки використовувати його lookup()метод, який модифікує внутрішній кеш.


9

mutable існує, як ви робите висновок, щоб дозволити змінювати дані в інакшій постійній функції.

Завдання полягає в тому, що у вас може бути функція, яка "нічого не робить" для внутрішнього стану об'єкта, і таким чином ви позначаєте функцію const, але можливо, вам дійсно потрібно буде змінити деякі стану об'єктів способами, які не впливають на його правильність функціональність.

Ключове слово може виступати підказкою для компілятора - теоретичний компілятор може помістити в пам'ять постійний об'єкт (наприклад, глобальний), який позначений лише для читання. Наявність mutableнатяків, що цього робити не слід.

Ось декілька поважних причин для оголошення та використання змінних даних:

  • Безпека нитки. Оголошення " mutable boost::mutexцілком розумне".
  • Статистика. Підрахунок кількості викликів до функції з урахуванням деяких або всіх її аргументів.
  • Пам'ятка. Вирахування дорогої відповіді, а потім її зберігання для подальшого використання, а не повторного обчислення.

2
Хороша відповідь, за винятком коментарів щодо того, щоб мутабел був "підказкою". Таким чином, здається, що елемент, який може змінюватися, іноді не змінюватиметься, якщо компілятор помістив об'єкт у ROM. Поведінка мутантів добре визначена.
Річард Корден

2
Крім розміщення об'єкта const у пам'яті, що використовується лише для читання, компілятор може також вирішити оптимізувати, наприклад, виклики fustntion fust з циклу. Змінений лічильник статистики в іншому випадку функції const все одно дозволить таку оптимізацію (і нараховує лише один виклик) замість того, щоб запобігти оптимізації лише заради підрахунку більшої кількості викликів.
Хаген фон Ейтцен

@HagenvonEitzen - я майже впевнений, що це неправильно. Компілятор не може підняти функції з циклу, якщо це не доведе, що немає побічних ефектів. Цей доказ, як правило, передбачає фактичну перевірку виконання функції (часто після її накреслення) і не покладаючись на неї const(і така інспекція буде успішною або невдалою незалежно від того constчи mutable). Просто оголошення функції constнедостатньо: constфункція вільна, щоб мати такі побічні ефекти, як зміна глобальної змінної або щось передане функції, тому це не є корисною гарантією цього доказу.
BeeOnRope

Зараз деякі компілятори мають спеціальні розширення, такі як _ccribute __ ((const)) gcc і __attribute __ ((чистий)), які _do мають такі ефекти , але це лише дотично пов'язане з 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

Мутабельний використовується, коли у вас є змінна всередині класу, яка використовується лише в цьому класі для сигналізації речей, таких як, наприклад, mutex або замок. Ця змінна не змінює поведінку класу, але необхідна для того, щоб реалізувати безпеку нитки самого класу. Таким чином, якби без "змінних", ви не змогли б мати функції "const", оскільки цю змінну потрібно буде змінити у всіх функціях, доступних для зовнішнього світу. Тому було внесено змінну для того, щоб зробити змінну члена для запису навіть функцією const.

Вказаний мутант повідомляє і компілятор, і читач, що це безпечно і очікується, що змінна члена може бути змінена в межах функції const-члена.


4

mutable використовується головним чином на деталі реалізації класу. Користувачеві класу про це не потрібно знати, тому метод, на який він вважає, «const» може бути const. Ваш приклад того, як мутекс може бути змінним, є хорошим канонічним прикладом.


4

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


3

Використовуйте "мутабельний", коли для речей, які ЛОГІЧНО не мають статусу для користувача (і, таким чином, повинні бути "const" одержувачі в API загальнодоступного класу), але НЕ є без громадянства в базовому ВПРОВАДЖЕННІ (код у вашому .cpp).

Випадки, якими я найчастіше користуюсь, - це ледача ініціалізація членів, що не містять "простих старих даних". А саме, це ідеально у вузьких випадках, коли таких членів дорого або збирати (обробляти), або переносити (пам'ять), і багато користувачів об’єкта ніколи їх не просять. У цій ситуації ви хочете ледачу конструкцію на задньому плані для продуктивності, оскільки 90% побудованих об'єктів взагалі ніколи не потребуватимуть їх створення, все ж вам потрібно представити правильний API без громадянства для громадського споживання.


2

Змінна змінює значення 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

У деяких випадках (як погано розроблені ітератори), класу потрібно зберігати відлік чи якесь інше побічне значення, що насправді не впливає на основний "стан" класу. Це найчастіше там, де я бачу вживаних змінних. Не змінюючи, ви змушені будете пожертвувати всіма властивостями вашого дизайну.

Мені здається, що більшу частину часу мені подобається зламати. Корисно в дуже дуже мало ситуацій.


1

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

Загалом, альтернативи використанню mutableключового слова зазвичай є статичною змінною у методі чи const_castтрюку.

Ще одне детальне пояснення знаходиться тут .


1
Я ніколи не чув про використання статичних членів як загальної альтернативи змінним членам. І const_castце лише тоді, коли ви знаєте (або отримали гарантію), що щось не зміниться (наприклад, при втручанні в бібліотеки C) або коли ви знаєте, що це не оголошено const. Тобто, зміна змінної 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

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


0

Один з найкращих прикладів, де ми використовуємо мутабельні файли, - у глибокій копії. у конструкторі копій ми надсилаємо const &objяк аргумент. Тож створений новий об’єкт буде мати постійний тип. Якщо ми хочемо змінити (в основному ми не змінимось, в рідкісних випадках ми можемо змінити) членів цього новоствореного об’єкта const, нам потрібно оголосити це як mutable.

mutableКлас зберігання може бути використаний лише для нестатичного члена даних, який не містить const. Член змінного даних класу може бути змінений, навіть якщо це частина об'єкта, який оголошується як 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.