La parola chiave "mutable" ha uno scopo diverso da quello di consentire la modifica della variabile da una funzione const?


527

Qualche tempo fa mi sono imbattuto in un codice che ha contrassegnato una variabile membro di una classe con la mutableparola chiave. Per quanto posso vedere, ti permette semplicemente di modificare una variabile in un constmetodo:

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

È questo l'unico uso di questa parola chiave o c'è di più di quello che sembra? Da allora ho usato questa tecnica in una classe, contrassegnando un boost::mutexcome mutabile che consente constalle funzioni di bloccarlo per motivi di sicurezza del thread, ma, a dire il vero, sembra un po 'un trucco.


2
Una domanda però, se non stai modificando nulla, perché devi usare un mutex in primo luogo? Voglio solo capire questo.
Misgevolution

@Misgevolution stai modificando qualcosa, stai solo controllando chi / come può fare la modifica tramite const. Un esempio davvero ingenuo, immagina che se do solo maniglie non costanti agli amici, i nemici ottengono una maniglia const. Gli amici possono modificare, i nemici no.
iheanyi,

1
Nota: ecco un ottimo esempio dell'uso della parola chiave mutable: stackoverflow.com/questions/15999123/…
Gabriel Staples

Vorrei che potesse essere usato per sovrascrivere const(di tipi), quindi non devo farlo class A_mutable{}; using A = A_mutable const; mutable_t<A> a;:, se voglio const-by-default, ovvero mutable A a;(mutevole esplicito) e A a;(const implicita).
alfC

Risposte:


351

Permette la differenziazione di const bit per bit e const logica. La const logica è quando un oggetto non cambia in modo visibile attraverso l'interfaccia pubblica, come nel tuo esempio di blocco. Un altro esempio potrebbe essere una classe che calcola un valore la prima volta che viene richiesto e memorizza nella cache il risultato.

Poiché c ++ 11 mutablepuò essere utilizzato su un lambda per indicare che le cose catturate dal valore sono modificabili (non lo sono per impostazione predefinita):

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
'mutabile' non influenza affatto la costanza bit a bit / logica. C ++ è solo bit bit const e la parola chiave "mutable" può essere utilizzata per escludere membri da questo controllo. Non è possibile ottenere una const "logica" in C ++ se non tramite astrazioni (ad es. SmartPtrs).
Richard Corden,

111
@Richard: ti manca il punto. Non esiste una parola chiave "const logica", vero, piuttosto, è una differenziazione concettuale che il programmatore fa per decidere quali membri dovrebbero essere esclusi essendo resi mutabili, in base alla comprensione di ciò che costituisce lo stato logico osservabile dell'oggetto.
Tony Delroy,

6
@ajay Sì, questo è l'intero punto di contrassegnare una variabile membro come mutabile, per consentirne la modifica in oggetti const.
KeithB

6
Perché è necessario mutevole su lambdas? Non sarebbe sufficiente acquisire la variabile per riferimento?
Giorgio,

11
@Giorgio: La differenza è che la modifica xall'interno della lambda rimane all'interno della lambda, ovvero la funzione lambda può solo modificare la propria copia di x. La modifica non è visibile all'esterno, l'originale xè ancora invariato. Considera che i lambda sono implementati come classi functor; le variabili acquisite corrispondono alle variabili membro.
Sebastian Mach,

138

La mutableparola chiave è un modo per perforare il constvelo che drappeggi sui tuoi oggetti. Se hai un riferimento const o un puntatore a un oggetto, non puoi modificarlo in alcun modo tranne quando e come è contrassegnato mutable.

Con il tuo constriferimento o puntatore sei costretto a:

  • accesso in sola lettura per tutti i membri di dati visibili
  • permesso di chiamare solo i metodi contrassegnati come const.

L' mutableeccezione lo rende così ora puoi scrivere o impostare membri di dati che sono contrassegnati mutable. Questa è l'unica differenza visibile esternamente.

Internamente quei constmetodi che sono visibili a te possono anche scrivere ai membri di dati che sono contrassegnati mutable. In sostanza, il velo const viene perforato in modo completo. Spetta completamente al progettista API assicurarsi che mutablenon distrugga il constconcetto e venga utilizzato solo in casi speciali utili. La mutableparola chiave aiuta perché indica chiaramente i membri dei dati che sono soggetti a questi casi speciali.

In pratica puoi usare constossessivamente tutto il tuo codebase (essenzialmente vuoi "infettare" il tuo codebase con la const"malattia"). In questo mondo, puntatori e riferimenti hanno constpochissime eccezioni, producendo codice che è più facile ragionare e comprendere. Per una digressione interessante cercare "trasparenza referenziale".

Senza la mutableparola chiave alla fine sarai costretto a utilizzare const_castper gestire i vari casi speciali utili che consente (memorizzazione nella cache, conteggio dei riferimenti, dati di debug, ecc.). Sfortunatamente const_castè significativamente più distruttivo che mutableperché costringe il client API a distruggere la constprotezione degli oggetti che sta usando. Inoltre provoca una constdistruzione diffusa : const_castl'inging un puntatore const o riferimento consente la scrittura senza restrizioni e il metodo che chiama l'accesso ai membri visibili. Al contrario, mutableil progettista dell'API deve esercitare un controllo accurato sulle consteccezioni, e di solito queste eccezioni sono nascoste nei constmetodi che operano su dati privati.

(NB Mi riferisco alla visibilità dei dati e del metodo alcune volte. Sto parlando di membri contrassegnati come pubblici o privati ​​o protetti, che è un tipo completamente diverso di protezione degli oggetti discussi qui .)


8
Inoltre, l'uso const_castper modificare una parte di un constoggetto produce un comportamento indefinito.
Brian

Non sono d'accordo perché costringe il client API a distruggere la protezione const degli oggetti . Se si sta utilizzando const_castper implementare mutazione del variabili membro in un constmetodo, non sarebbe chiedere al cliente di fare il cast - che ci si farlo all'interno del metodo da const_casting this. Fondamentalmente ti consente di bypassare la costanza su membri arbitrari in un sito di chiamata specifico , mentre mutablerimuoviamo const su un membro specifico in tutti i siti di chiamata. Quest'ultimo è in genere ciò che si desidera per l'uso tipico (memorizzazione nella cache, statistiche), ma a volte const_cast si adatta allo schema.
BeeOnRope,

1
Il const_castmodello si adatta meglio in alcuni casi, ad esempio quando si desidera modificare temporaneamente un membro, quindi ripristinarlo (praticamente come boost::mutex). Il metodo è logicamente costante poiché lo stato finale è lo stesso di quello iniziale, ma si desidera apportare tale modifica temporanea. const_castpuò essere utile lì perché ti consente di scartare const in modo specifico in quel metodo in cui la mutazione verrà annullata, ma mutablenon sarebbe appropriata poiché rimuoverà la protezione const da tutti i metodi, che non necessariamente seguono tutti il ​​"do , annulla "modello.
BeeOnRope,

2
L'eventuale posizionamento di const definito oggetto in memoria di sola lettura (più in generale, della memoria contrassegnata di sola lettura) e il linguaggio standard associata che permette questo rende const_castuna possibile bomba a orologeria però. mutablenon presenta alcun problema in quanto tali oggetti non possono essere collocati nella memoria di sola lettura.
BeeOnRope,

75

Il tuo utilizzo con boost :: mutex è esattamente lo scopo di questa parola chiave. Un altro uso è la memorizzazione nella cache dei risultati interna per velocizzare l'accesso.

Fondamentalmente, "mutabile" si applica a qualsiasi attributo di classe che non influisce sullo stato visibile esternamente dell'oggetto.

Nel codice di esempio nella tua domanda, mutable potrebbe essere inappropriato se il valore di done_ influenza lo stato esterno, dipende da cosa c'è nel ...; parte.


35

Mutabile è per contrassegnare l'attributo specifico come modificabile all'interno dei constmetodi. Questo è il suo unico scopo. Pensa attentamente prima di usarlo, perché il tuo codice sarà probabilmente più pulito e più leggibile se cambi il design piuttosto che usarlo mutable.

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

Quindi se la follia di cui sopra non è ciò che è mutevole, a cosa serve? Ecco il caso sottile: mutabile è per il caso in cui un oggetto è logicamente costante, ma in pratica deve cambiare. Questi casi sono pochi e lontani tra loro, ma esistono.

Esempi forniti dall'autore includono la memorizzazione nella cache e variabili di debug temporanee.


2
Penso che questo link fornisca il miglior esempio di uno scenario in cui l'utilità mutabile è utile, sembra quasi che vengano utilizzati esclusivamente per il debug. (per un uso corretto)
enthusiasticgeek,

L'uso di mutablepuò rendere il codice più leggibile e più pulito. Nel seguente esempio, readpuò essere constcome previsto. `mutable m_mutex; Contenitore m_container; void add (Item item) {Lockguard lock (m_mutex); m_container.pushback (voce); } Item read () const {Lockguard lock (m_mutex); return m_container.first (); } `
Th. Thielemann,

C'è un caso d'uso estremamente popolare: i riferimenti contano.
Seva Alekseyev,

33

È utile in situazioni in cui è stato nascosto lo stato interno come una cache. Per esempio:

classe HashTable
{
...
pubblico:
    ricerca stringhe (chiave stringa) const
    {
        if (key == lastKey)
            return lastValue;

        valore stringa = lookupInternal (chiave);

        lastKey = chiave;
        lastValue = value;

        valore di ritorno;
    }

privato:
    stringa mutabile lastKey, lastValue;
};

E poi puoi avere un const HashTableoggetto che usa ancora il suo lookup()metodo, che modifica la cache interna.


9

mutable esiste come si deduce per consentire a uno di modificare i dati in una funzione altrimenti costante.

L'intento è che potresti avere una funzione che "non fa nulla" allo stato interno dell'oggetto, quindi contrassegni la funzione const, ma potresti davvero aver bisogno di modificare alcuni stati degli oggetti in modi che non influiscono sul suo corretto funzionalità.

La parola chiave può fungere da suggerimento per il compilatore: un compilatore teorico può inserire un oggetto costante (come un globale) in memoria contrassegnato come di sola lettura. La presenza di mutablesuggerimenti che ciò non dovrebbe essere fatto.

Ecco alcuni motivi validi per dichiarare e utilizzare i dati mutabili:

  • Sicurezza del filo. Dichiarare a mutable boost::mutexè perfettamente ragionevole.
  • Statistiche. Contando il numero di chiamate a una funzione, dati alcuni o tutti i suoi argomenti.
  • Memoizzazione. Calcolare una risposta costosa e quindi memorizzarla per riferimento futuro anziché ricalcolarla di nuovo.

2
Buona risposta, tranne che per i commenti riguardanti il ​​mutevole essere un "suggerimento". Questo fa sembrare che il membro mutabile a volte non sia mutabile se il compilatore ha inserito l'oggetto nella ROM. Il comportamento dei mutabili è ben definito.
Richard Corden,

2
Oltre a mettere un oggetto const nella memoria di sola lettura, il compilatore può anche decidere di ottimizzare le chiamate di costanza da un loop, ad esempio. Un contatore di statistiche mutabili in una funzione altrimenti const consentirà comunque tale ottimizzazione (e conterà solo una chiamata) invece di impedire l'ottimizzazione solo per il conteggio di più chiamate.
Hagen von Eitzen,

@HagenvonEitzen - Sono abbastanza sicuro che non sia corretto. Un compilatore non può sollevare le funzioni da un loop a meno che non possa provare che non ci sono effetti collaterali. Tale prova generalmente implica effettivamente l'ispezione dell'implementazione della funzione (spesso dopo che è stata sottolineata) e non fare affidamento const(e tale ispezione avrà successo o fallirà indipendentemente da consto mutable). Dichiarare semplicemente la funzione constnon è sufficiente: una constfunzione è libera di avere effetti collaterali come la modifica di una variabile globale o qualcosa che viene passato alla funzione, quindi non è una garanzia utile per quella prova.
BeeOnRope,

Ora, alcuni compilatori hanno estensioni speciali come _attribute __ ((const)) e __attribute __ ((puro)) di gcc, che _ hanno tali effetti , ma ciò è solo tangenzialmente correlato alla constparola chiave in C ++.
BeeOnRope,

8

Bene, sì, è quello che fa. Lo uso per i membri che sono modificati da metodi che non cambiano logicamente lo stato di una classe, ad esempio per velocizzare le ricerche implementando una cache:

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

Ora, devi usarlo con cura: i problemi di concorrenza sono una grande preoccupazione, poiché un chiamante potrebbe presumere che siano thread-safe se solo usando constmetodi. E, naturalmente, la modifica dei mutabledati non dovrebbe cambiare il comportamento dell'oggetto in alcun modo significativo, qualcosa che potrebbe essere violato dall'esempio che ho fornito se, ad esempio, ci si aspettasse che le modifiche scritte sul disco fossero immediatamente visibili all'app .


6

Mutabile viene utilizzato quando all'interno della classe è presente una variabile che viene utilizzata solo all'interno di quella classe per segnalare cose come ad esempio un mutex o un lock. Questa variabile non modifica il comportamento della classe, ma è necessaria per implementare la sicurezza dei thread della classe stessa. Quindi se senza "mutable", non saresti in grado di avere funzioni "const" perché questa variabile dovrà essere cambiata in tutte le funzioni che sono disponibili al mondo esterno. Pertanto, è stato introdotto il parametro mutable per rendere scrivibile una variabile membro anche da una funzione const.

Il mutabile specificato informa sia il compilatore che il lettore che è sicuro e previsto che una variabile membro possa essere modificata all'interno di una funzione membro const.


4

mutable viene utilizzato principalmente su un dettaglio di implementazione della classe. L'utente della classe non ha bisogno di saperlo, quindi secondo lui il metodo "dovrebbe" essere const può essere. Il tuo esempio di avere un mutex mutabile è un buon esempio canonico.


4

Il tuo uso non è un hack, anche se come molte cose in C ++, mutable può essere un hack per un programmatore pigro che non vuole tornare indietro e contrassegnare qualcosa che non dovrebbe essere const come non-const.


3

Usa "mutable" quando per cose che sono LOGICALLY apolidi per l'utente (e quindi dovrebbero avere "const" getter nelle API della classe pubblica) ma NON sono apolidi nell'ATTUAZIONE sottostante (il codice nel tuo .cpp).

I casi che uso più frequentemente sono l'inizializzazione lenta di membri "normali dati" senza stato. Vale a dire, è ideale nei casi stretti in cui tali membri sono costosi da costruire (processore) o portare in giro (memoria) e molti utenti dell'oggetto non li chiederanno mai. In quella situazione si desidera una costruzione pigra sul back-end per le prestazioni, poiché il 90% degli oggetti costruiti non avrà mai bisogno di costruirli affatto, tuttavia è ancora necessario presentare l'API stateless corretta per il consumo pubblico.


2

Mutabile cambia il significato di const da const bit a bit in const logica per la classe.

Ciò significa che le classi con membri mutabili sono più costanti bit a bit e non appariranno più nelle sezioni di sola lettura dell'eseguibile.

Inoltre, modifica il controllo del tipo consentendo constalle funzioni membro di cambiare membri mutabili senza usare 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.
}

Vedi le altre risposte per maggiori dettagli, ma volevo sottolineare che non è solo per la sicurezza dei tipi e che influenza il risultato compilato.


1

In alcuni casi (come gli iteratori mal progettati), la classe deve tenere un conto o qualche altro valore accidentale, che non influisce realmente sullo "stato" principale della classe. Questo è molto spesso dove vedo mutevole usato. Senza mutevole, saresti costretto a sacrificare l'intera costanza del tuo design.

Anche a me sembra un hack per la maggior parte del tempo. Utile in pochissime situazioni.


1

L'esempio classico (come menzionato in altre risposte) e l'unica situazione che ho visto mutable parola chiave utilizzata finora, è per memorizzare nella cache il risultato di un Getmetodo complicato , in cui la cache è implementata come membro di dati della classe e non come variabile statica nel metodo (per motivi di condivisione tra più funzioni o pulizia semplice).

In generale, le alternative all'uso della mutableparola chiave sono generalmente una variabile statica nel metodo o nelconst_cast trucco.

Un'altra spiegazione dettagliata è qui .


1
Non ho mai sentito parlare dell'utilizzo di membri statici come alternativa generale ai membri mutabili. Ed const_castè solo per quando sai (o ti è stato garantito) che qualcosa non cambierà (ad esempio quando interferisci con le librerie C) o quando sai che non è stato dichiarato const. Vale a dire, la modifica di una variabile const con cast costante comporta un comportamento indefinito.
Sebastian Mach,

1
@phresnel Per "variabili statiche" intendevo le variabili automatiche statiche nel metodo (che rimangono tra le chiamate). E const_castpuò essere usato per modificare un membro della classe in un constmetodo, che è ciò a cui mi
riferivo

1
Non è stato chiaro per me, come hai scritto "in generale" :) Per quanto riguarda la modifica const_cast, come detto, questo è consentito solo quando l'oggetto non è stato dichiarato const. Ad esempio const Frob f; f.something();, con void something() const { const_cast<int&>(m_foo) = 2;risultati in un comportamento indefinito.
Sebastian Mach,

1

Il mutabile può essere utile quando si esegue l'override di una funzione virtuale const e si desidera modificare la variabile membro della classe figlio in quella funzione. Nella maggior parte dei casi non si desidera modificare l'interfaccia della classe di base, quindi è necessario utilizzare la propria variabile membro mutabile.


1

La parola chiave mutabile è molto utile quando si creano stub a scopo di test di classe. È possibile stub una funzione const ed essere comunque in grado di aumentare i contatori (mutabili) o qualunque funzionalità di test aggiunta al proprio stub. Ciò mantiene intatta l'interfaccia della classe stubbed.


0

Uno dei migliori esempi in cui usiamo mutable è, in copia profonda. nel costruttore di copie inviamo const &objcome argomento. Quindi il nuovo oggetto creato sarà di tipo costante. Se vogliamo cambiare (per lo più non cambieremo, in rari casi potremmo cambiare) i membri in questo oggetto const appena creato, dobbiamo dichiararlo come mutable.

mutablela classe di archiviazione può essere utilizzata solo su un membro di dati non const non statico di una classe. Il membro di dati mutabili di una classe può essere modificato anche se fa parte di un oggetto dichiarato come 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

Nell'esempio sopra, siamo in grado di cambiare il valore della variabile membro xsebbene faccia parte di un oggetto dichiarato come const. Questo perché la variabile xè dichiarata mutabile. Ma se provi a modificare il valore della variabile membro y, il compilatore genererà un errore.


-1

La stessa parola chiave "mutable" è in realtà una parola chiave riservata. Spesso viene utilizzata per variare il valore della variabile costante. Se si desidera avere più valori di un constsnt, utilizzare la parola chiave mutable.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.