Czy słowo kluczowe „mutable” ma jakiś cel inny niż umożliwienie modyfikacji zmiennej przez funkcję const?


527

Jakiś czas temu natknąłem się na kod, który oznaczał zmienną składową klasy mutablesłowem kluczowym. O ile widzę, po prostu pozwala modyfikować zmienną w constmetodzie:

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

Czy to jedyne użycie tego słowa kluczowego, czy jest w nim coś więcej niż na pierwszy rzut oka? Od tego czasu korzystałem z tej techniki na zajęciach, oznaczając boost::mutexzmienną, pozwalającą constfunkcjom blokować ją ze względów bezpieczeństwa wątków, ale, szczerze mówiąc, wydaje się to trochę hack.


2
Pytanie jednak, jeśli niczego nie modyfikujesz, dlaczego najpierw potrzebujesz muteksu? Chcę to po prostu zrozumieć.
Misgevolution,

@Misgevolution modyfikujesz coś, po prostu kontrolujesz, kto / jak może dokonać modyfikacji za pomocą const. Naprawdę naiwny przykład, wyobraź sobie, że jeśli daję tylko niezamknięte uchwyty przyjaciołom, wrogowie mają stały uchwyt. Przyjaciele mogą modyfikować, wrogowie nie.
iheanyi

1
Uwaga: oto świetny przykład użycia słowa kluczowego mutable: stackoverflow.com/questions/15999123/...
Gabriel Staples,

Chciałbym, żeby można go było zastąpić const(typów), więc nie muszę tego robić: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;jeśli chcę domyślnie const, tj. mutable A a;(Jawnie zmienny) i A a;(domyślny const).
alfC

Odpowiedzi:


351

Pozwala na rozróżnienie stałej bitowej i logicznej. Logiczna stała ma miejsce, gdy obiekt nie zmienia się w sposób widoczny przez interfejs publiczny, jak na przykład w przypadku blokowania. Innym przykładem może być klasa, która oblicza wartość przy pierwszym żądaniu i buforuje wynik.

Ponieważ c ++ 11 mutablemoże być użyty na lambda do oznaczenia, że ​​rzeczy przechwycone przez wartość są modyfikowalne (nie są domyślnie):

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” w ogóle nie wpływa na bitową / logiczną stałość. C ++ jest tylko bitową const, a słowo kluczowe „mutable” może być użyte do wykluczenia członków z tego sprawdzania. Nie jest możliwe osiągnięcie „logicznej” stałej w C ++ inaczej niż poprzez abstrakcje (np. SmartPtrs).
Richard Corden,

111
@ Richard: nie rozumiesz sedna sprawy. Nie ma słowa kluczowego „logical const”, to prawda. Jest to pojęciowe rozróżnienie, które programista decyduje, które elementy należy wykluczyć, zmieniając je, w oparciu o zrozumienie tego, co stanowi logiczny obserwowalny stan obiektu.
Tony Delroy

6
@ajay Tak, to jest cały punkt oznaczenia zmiennej członka jako zmiennej, aby umożliwić jej zmianę w obiektach stałych.
KeithB,

6
Dlaczego potrzebna jest mutowalność na lambdach? Czy nie wystarczy przechwycić zmienną przez referencję?
Giorgio

11
@Giorgio: Różnica polega na tym, że modyfikacja xw lambda pozostaje w lambda, tzn. Funkcja lambda może modyfikować tylko własną kopię x. Zmiana nie jest widoczna na zewnątrz, oryginał xpozostaje niezmieniony. Weź pod uwagę, że lambda są implementowane jako klasy funktorów; przechwycone zmienne odpowiadają zmiennym składowym.
Sebastian Mach

138

Słowo mutablekluczowe jest sposobem na przebicie constzasłony, którą zasłaniasz swoje przedmioty. Jeśli masz stałe odniesienie lub wskaźnik do obiektu, nie możesz modyfikować tego obiektu w żaden sposób, z wyjątkiem tego, kiedy i jak jest oznaczony mutable.

Z constodniesieniem lub wskaźnik jesteś ograniczony do:

  • dostęp tylko do odczytu dla widocznych członków danych
  • uprawnienie do wywoływania tylko metod oznaczonych jako const.

mutableWyjątek sprawia, że tak można teraz pisać lub zestaw danych użytkowników, które są oznaczone mutable. To jedyna widoczna z zewnątrz różnica.

Wewnętrznie te constmetody, które są widoczne, można również zapisywać do oznaczonych elementów danych mutable. Zasadniczo konstelacja jest wszechstronnie przebita. Projektant interfejsu API musi całkowicie upewnić się, że mutablenie niszczy constkoncepcji i jest używany tylko w użytecznych specjalnych przypadkach. Słowo mutablekluczowe pomaga, ponieważ wyraźnie oznacza członków danych, którzy podlegają tym szczególnym przypadkom.

W praktyce możesz używać constobsesyjnie w całej bazie kodu (zasadniczo chcesz „zainfekować” swoją bazę kodową const„chorobą”). W tym świecie wskaźniki i referencje są constz nielicznymi wyjątkami, dając kod łatwiejszy do zrozumienia i zrozumienia. W celu uzyskania interesującej dygresji wyszukaj „przejrzystość referencyjna”.

Bez mutablesłowa kluczowego będziesz w końcu zmuszony do const_castobsługi różnych przydatnych specjalnych przypadków, na które pozwala (buforowanie, liczenie odwołań, dane debugowania itp.). Niestety const_castjest znacznie bardziej destrukcyjny niż mutabledlatego, że zmusza klienta API do zniszczenia constochrony obiektów, których używa. Dodatkowo powoduje powszechne constzniszczenie: const_castwprowadzenie stałego wskaźnika lub odwołania umożliwia swobodny zapis i wywołanie metody dostępu do widocznych elementów. W przeciwieństwie do tego mutablewymaga się od projektanta interfejsu API sprawowania drobiazgowej kontroli nad constwyjątkami i zwykle wyjątki te są ukryte w constmetodach działających na danych prywatnych.

(NB I odnoszą się do danych i sposób widoczności kilka razy. Mówię o członków oznaczonych jako prywatny lub publiczny w porównaniu z chronionymi, który jest zupełnie inny rodzaj ochrony obiektu omawiane tutaj ).


8
Ponadto użycie const_castdo zmodyfikowania części constobiektu daje niezdefiniowane zachowanie.
Brian

Nie zgadzam się z tym, ponieważ zmusza klienta API do zniszczenia stałej ochrony obiektów . Jeśli używałeś const_castdo implementacji mutacji zmiennych składowych w constmetodzie, nie poprosiłbyś klienta o wykonanie rzutowania - zrobiłbyś to w ramach metody przez const_casting this. Zasadniczo pozwala to ominąć stałość dowolnych członków w określonej witrynie wywoławczej , a jednocześnie mutablepozwala usunąć stałą w określonym elemencie we wszystkich witrynach wywoławczych. To drugie jest zwykle tym, czego chcesz do typowego zastosowania (buforowanie, statystyki), ale czasami const_cast pasuje do wzorca.
BeeOnRope

1
const_castWzór pasuje lepiej w niektórych przypadkach, na przykład gdy chcesz tymczasowo zmodyfikować element, a następnie przywrócić je (prawie jak boost::mutex). Metoda jest logicznie stała, ponieważ stan końcowy jest taki sam jak początkowy, ale chcesz wprowadzić tę przejściową zmianę. const_castmoże być przydatna, ponieważ pozwala odrzucić const w tej metodzie, gdyby mutacja została cofnięta, ale mutablenie byłaby odpowiednia, ponieważ usunęłaby const const ze wszystkich metod, które niekoniecznie wszystkie są zgodne z „do” , cofnij ”.
BeeOnRope

2
Możliwe umieszczenie stałego obiektu zdefiniowanego w pamięci tylko do odczytu (bardziej ogólnie, pamięć oznaczona jako tylko do odczytu) i związany z nią standardowy język, który pozwala na to, może jednak const_caststworzyć bombę zegarową. mutablenie ma takiego problemu, ponieważ takich obiektów nie można umieścić w pamięci tylko do odczytu.
BeeOnRope

75

Twoje użycie z boost :: mutex jest dokładnie tym, do czego przeznaczone jest to słowo kluczowe. Innym zastosowaniem jest buforowanie wyników wewnętrznych w celu przyspieszenia dostępu.

Zasadniczo „zmienny” dotyczy dowolnego atrybutu klasy, który nie wpływa na zewnętrznie widoczny stan obiektu.

W przykładowym kodzie w pytaniu zmienna może być nieodpowiednia, jeśli wartość done_ wpływa na stan zewnętrzny, zależy to od tego, co znajduje się w ...; część.


35

Zmienna służy do oznaczania określonego atrybutu jako modyfikowalnego z poziomu constmetod. To jest jego jedyny cel. Zastanów się, zanim go użyjesz, ponieważ kod będzie prawdopodobnie bardziej przejrzysty i czytelny, jeśli zmienisz projekt, a nie użyjesz mutable.

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

Więc jeśli powyższe szaleństwo nie jest tym, co jest zmienne, to po co? Oto subtelny przypadek: zmienny dotyczy przypadku, w którym obiekt jest logicznie stały, ale w praktyce musi się zmienić. Te przypadki są nieliczne i dalekie, ale istnieją.

Przykłady podane przez autora obejmują buforowanie i tymczasowe zmienne debugowania.


2
Myślę, że ten link stanowi najlepszy przykład scenariusza, w którym zmienna jest pomocna. Wydaje się, że są one używane wyłącznie do debugowania. (przy prawidłowym użyciu)
entuzjastycznie

Zastosowanie mutablemoże sprawić, że kod będzie bardziej czytelny i przejrzysty. W poniższym przykładzie wartość readmoże być constzgodna z oczekiwaniami. `mutable m_mutex; Pojemnik m_container; void add (Przedmiot pozycji) {Lockguard lock (m_mutex); m_container.pushback (item); } Element read () const {Lockguard lock (m_mutex); return m_container.first (); } `
Th. Thielemann

Jest jeden bardzo popularny przypadek użycia: liczy się ref.
Seva Alekseyev

33

Jest to przydatne w sytuacjach, w których masz ukryty stan wewnętrzny, taki jak pamięć podręczna. Na przykład:

klasa HashTable
{
...
publiczny:
    wyszukiwanie łańcucha (klucz łańcucha) const
    {
        if (klucz == lastKey)
            return lastValue;

        wartość ciągu = lookupInternal (key);

        lastKey = klucz;
        lastValue = wartość;

        wartość zwracana;
    }

prywatny:
    łańcuch mutable lastKey, lastValue;
};

I wtedy możesz mieć const HashTableobiekt nadal korzystający z jego lookup()metody, która modyfikuje wewnętrzną pamięć podręczną.


9

mutable istnieje, gdy wnioskujesz, aby umożliwić modyfikowanie danych w funkcji stałej w przeciwnym razie.

Chodzi o to, abyś mógł mieć funkcję, która „nic nie robi” wewnętrznemu stanowi obiektu, więc oznaczysz tę funkcję const, ale naprawdę może być konieczna modyfikacja niektórych stanów obiektów w sposób, który nie wpływa na jej poprawność funkcjonalność.

Słowo kluczowe może działać jako wskazówka dla kompilatora - teoretyczny kompilator może umieścić stały obiekt (taki jak globalny) w pamięci, który został oznaczony jako tylko do odczytu. Obecność mutablewskazówek, że nie należy tego robić.

Oto kilka ważnych powodów, aby zadeklarować i użyć danych podlegających zmianom:

  • Bezpieczeństwo wątków. Deklaracja a mutable boost::mutexjest całkowicie uzasadniona.
  • Statystyka. Liczenie liczby wywołań funkcji, z uwzględnieniem niektórych lub wszystkich jej argumentów.
  • Zapamiętywanie. Obliczanie drogiej odpowiedzi, a następnie przechowywanie jej do przyszłego wykorzystania, zamiast ponownego obliczania.

2
Dobra odpowiedź, z wyjątkiem komentarza dotyczącego zmienności jako „podpowiedzi”. To sprawia, że ​​wydaje się, że zmienny element czasami nie będzie mutowalny, jeśli kompilator umieści obiekt w pamięci ROM. Zachowanie mutable jest dobrze określone.
Richard Corden,

2
Oprócz umieszczenia stałego obiektu w pamięci tylko do odczytu, kompilator może również zdecydować na przykład o zoptymalizowaniu wywołania funkcji stałej z pętli. Zmienny licznik statystyk w funkcji inaczej const nadal pozwoli na taką optymalizację (i zliczenie tylko jednego połączenia) zamiast zapobiegać optymalizacji tylko w celu zliczenia większej liczby połączeń.
Hagen von Eitzen,

@HagenvonEitzen - Jestem prawie pewien, że to nieprawda. Kompilator nie może wyciągać funkcji z pętli, chyba że może udowodnić, że nie występują żadne skutki uboczne. Dowód ten zasadniczo obejmuje sprawdzenie wykonania funkcji (często po jej wstawieniu) i nie poleganie na niej const(a taka kontrola zakończy się powodzeniem lub niepowodzeniem niezależnie od constlub mutable). Samo zadeklarowanie funkcji constnie wystarczy: constfunkcja może mieć skutki uboczne, takie jak modyfikacja zmiennej globalnej lub coś przekazanego do funkcji, więc nie jest to przydatna gwarancja dla tego dowodu.
BeeOnRope

Teraz niektóre kompilatory mają specjalne rozszerzenia, takie jak _ccribute __ gcc __ ((const)) i __attribute __ ((pure)), które mogą mieć takie efekty , ale są stycznie związane ze constsłowem kluczowym w C ++.
BeeOnRope

8

Tak, właśnie to robi. Używam go dla członków zmodyfikowanych metodami, które nie logicznie zmieniają stanu klasy - na przykład w celu przyspieszenia wyszukiwania poprzez implementację pamięci podręcznej:

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;
};

Teraz musisz używać tego ostrożnie - problemy z współbieżnością są dużym problemem, ponieważ osoba dzwoniąca może założyć, że jest bezpieczna dla wątków, jeśli używa tylko constmetod. I oczywiście modyfikowanie mutabledanych nie powinno w żaden znaczący sposób wpływać na zachowanie obiektu, co może zostać naruszone w podanym przeze mnie przykładzie, jeśli na przykład spodziewano się, że zmiany zapisane na dysku będą natychmiast widoczne dla aplikacji .


6

Zmienna jest używana, gdy masz zmienną wewnątrz klasy, która jest używana tylko w tej klasie do sygnalizowania rzeczy takich jak na przykład muteks lub blokada. Ta zmienna nie zmienia zachowania klasy, ale jest niezbędna do wdrożenia bezpieczeństwa wątków samej klasy. Zatem bez „mutable” nie byłoby możliwe posiadanie funkcji „const”, ponieważ zmienna ta będzie musiała zostać zmieniona we wszystkich funkcjach dostępnych dla świata zewnętrznego. Dlatego zmienna została wprowadzona, aby zmienna składowa była zapisywalna nawet przez funkcję const.

Zmienna określona informuje zarówno kompilator, jak i czytnik, że jest bezpieczna i oczekuje się, że zmienna składowa może być modyfikowana w funkcji stałej składowej.


4

Zmienna jest używana głównie w szczegółach implementacji klasy. Użytkownik klasy nie musi o tym wiedzieć, dlatego metoda, którą uważa, że ​​„powinna” być stała, może być. Twój przykład muteksa, który można mutować, jest dobrym przykładem kanonicznym.


4

Używanie go nie jest hackowaniem, chociaż jak wiele rzeczy w C ++, mutable może być hackiem dla leniwego programisty, który nie chce cofać się i oznaczać czegoś, co nie powinno być const jako non-const.


3

Użyj „mutable”, gdy dla rzeczy, które są logicznie bezstanowe dla użytkownika (a zatem powinny mieć „const” pobierające w interfejsach API klasy publicznej), ale NIE są bezstanowe w podstawowej IMPLEMENTACJI (kod w twoim .cpp).

Przypadki, których najczęściej używam, to leniwa inicjalizacja niepaństwowych członków „zwykłych starych danych”. Mianowicie jest idealny w wąskich przypadkach, gdy takie elementy są drogie albo do zbudowania (procesor), albo do noszenia (pamięć), a wielu użytkowników obiektu nigdy o nie nie poprosi. W tej sytuacji chcesz leniwej konstrukcji na zapleczu dla wydajności, ponieważ 90% zbudowanych obiektów nigdy nie będzie musiało ich wcale budować, a mimo to musisz przedstawić poprawny bezstanowy interfejs API do publicznego użytku.


2

Zmienna zmienia znaczenie constz stałej bitowej na stałą logiczną dla klasy.

Oznacza to, że klasy ze zmiennymi elementami są już bitami const i nie będą już pojawiać się w sekcjach pliku wykonywalnego tylko do odczytu.

Co więcej, modyfikuje sprawdzanie typu, pozwalając constfunkcjom członkowskim zmieniać zmienne elementy bez użycia 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.
}

Zobacz inne odpowiedzi, aby uzyskać więcej szczegółów, ale chciałem podkreślić, że nie chodzi tylko o bezpieczeństwo typu i że wpływa to na skompilowany wynik.


1

W niektórych przypadkach (np. Źle zaprojektowane iteratory) klasa musi zachować liczbę lub inną przypadkową wartość, która tak naprawdę nie wpływa na główny „stan” klasy. To jest najczęściej tam, gdzie używam mutable. Bez możliwości modyfikacji będziesz musiał poświęcić całą trwałość swojego projektu.

Dla mnie to też jest hack. Przydatny w bardzo niewielu sytuacjach.


1

Klasyczny przykład (jak wspomniano w innych odpowiedziach) i jedyna sytuacja, w której do tej pory widziałem mutablesłowo kluczowe, dotyczy buforowania wyniku skomplikowanej Getmetody, w której pamięć podręczna jest implementowana jako element danych klasy, a nie jako zmienna statyczna w metodzie (ze względu na współdzielenie kilku funkcji lub zwykłą czystość).

Zasadniczo alternatywami dla użycia mutablesłowa kluczowego są zwykle zmienna statyczna w metodzie lub const_castsztuczce.

Inne szczegółowe wyjaśnienie znajduje się tutaj .


1
Nigdy nie słyszałem o używaniu elementów statycznych jako ogólnej alternatywy dla elementów zmiennych. I const_castto tylko wtedy, gdy wiesz (lub masz gwarancję), że coś się nie zmieni (np. Podczas ingerowania w biblioteki C) lub gdy wiesz, że nie zostało to zadeklarowane jako const. Tzn. Modyfikacja stałej const rzutowanej powoduje w wyniku niezdefiniowane zachowanie.
Sebastian Mach

1
@ phresnel Przez „zmienne statyczne” miałem na myśli statyczne zmienne automatyczne w metodzie (które pozostają w trakcie wywołań). I const_castmoże być użyty do modyfikacji członka klasy w constmetodzie, o czym mówiłem ...
Daniel Hershcovich

1
Nie było to dla mnie jasne, ponieważ napisałeś „ogólnie” :) W odniesieniu do modyfikacji poprzez const_cast, jak powiedziano, jest to dozwolone tylko wtedy, gdy obiekt nie został zadeklarowany const. Na przykład const Frob f; f.something();, z void something() const { const_cast<int&>(m_foo) = 2;wynikami w niezdefiniowanej zachowań.
Sebastian Mach

1

Zmienna może być przydatna, gdy przesłonisz stałą funkcję wirtualną i chcesz zmodyfikować zmienną członka klasy podrzędnej w tej funkcji. W większości przypadków nie chciałbyś zmieniać interfejsu klasy podstawowej, więc musisz użyć własnej zmiennej zmiennej składowej.


1

Zmienne słowo kluczowe jest bardzo przydatne podczas tworzenia kodów pośredniczących na potrzeby testów klasowych. Możesz podstawić funkcję const i nadal być w stanie zwiększyć (modyfikować) liczniki lub dowolną funkcjonalność testową, którą dodałeś do swojego kodu pośredniczącego. Utrzymuje to interfejs nienaruszonej klasy.


0

Jednym z najlepszych przykładów, w których używamy mutable, jest głębokie kopiowanie. w konstruktorze kopii wysyłamy const &objjako argument. Tak więc utworzony nowy obiekt będzie typu stałego. Jeśli chcemy zmienić (przeważnie tego nie zmienimy, w rzadkich przypadkach możemy zmienić) członkowie tego nowo utworzonego obiektu const musimy zadeklarować jako mutable.

mutableklasa pamięci może być używana tylko na niestatycznym, stałym obiekcie danych należącym do klasy. Zmienny element danych klasy może być modyfikowany, nawet jeśli jest częścią obiektu zadeklarowanego jako 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

W powyższym przykładzie jesteśmy w stanie zmienić wartość zmiennej składowej x składowej, chociaż jest ona częścią obiektu zadeklarowanego jako const. Wynika to z faktu, że zmienna xjest zadeklarowana jako zmienna . Ale jeśli spróbujesz zmodyfikować wartość zmiennej yskładowej, kompilator zgłosi błąd.


-1

Samo słowo kluczowe „mutable” jest faktycznie słowem kluczowym zastrzeżonym. Często jest używane do zmiany wartości zmiennej stałej. Jeśli chcesz mieć wiele wartości constsnt, użyj słowa kluczowego mutable.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.