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

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

  •  01-07-2019
  •  | 
  •  

Domanda

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

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 :: mutex come mutabile consentendo alle funzioni const di bloccarlo per motivi di sicurezza del thread, ma, a dire il vero, sembra un po 'un trucco.

È stato utile?

Soluzione

Permette la differenziazione di const bitwise e const logici. 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.

Dal momento che c ++ 11 mutable può essere usato su un lambda per indicare che le cose catturate dal valore sono modificabili (non lo sono di default):

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

Altri suggerimenti

La parola chiave mutable è un modo per perforare il velo const 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 riferimento o puntatore const sei costretto a:

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

L'eccezione mutable consente di scrivere o impostare membri di dati contrassegnati con mutable . Questa è l'unica differenza visibile esternamente.

Internamente quei metodi const 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 mutable non distrugga il concetto const e venga utilizzato solo in casi speciali utili. La parola chiave mutable aiuta perché indica chiaramente i membri dei dati soggetti a questi casi speciali.

In pratica puoi usare const in modo ossessivo su tutta la tua base di codice (essenzialmente vuoi "infettare" la tua base di codice con la const " malattia "). In questo mondo, puntatori e riferimenti sono const con pochissime eccezioni, producendo codice che è più facile ragionare e comprendere. Per una digressione interessante cerca "trasparenza referenziale".

Senza la parola chiave mutable alla fine sarai costretto a usare const_cast per gestire i vari casi speciali utili che consente (memorizzazione nella cache, conteggio dei riferimenti, dati di debug, ecc.) . Sfortunatamente const_cast è significativamente più distruttivo di mutable perché forza l'API client a distruggere la protezione const degli oggetti (s) che sta usando. Inoltre provoca la distruzione diffusa di const : const_cast l'ingestione di un puntatore o riferimento const consente l'accesso senza restrizioni alla scrittura e al metodo di chiamata ai membri visibili. Al contrario, mutable richiede al progettista dell'API di esercitare un controllo accurato sulle eccezioni const , e di solito queste eccezioni sono nascoste nei metodi const che operano in privato i dati.

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

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, la modifica può essere inappropriata se il valore di done_ influenza lo stato esterno, dipende da cosa c'è nel ...; parte.

Mutabile è per contrassegnare l'attributo specifico come modificabile dai metodi const . Questo è il suo unico scopo. Rifletti attentamente prima di usarlo, perché il tuo codice sarà probabilmente più pulito e più leggibile se cambi il design anziché usare mutable .

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

  

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

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

È utile in situazioni in cui si ha uno stato interno nascosto come una cache. Ad esempio:

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

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

Beh, sì, è quello che fa. Lo uso per i membri che sono modificati da metodi che non logicamente cambiano lo stato di una classe - per 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 utilizza solo i metodi const . E, naturalmente, la modifica dei dati mutable 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 in il disco sarebbe immediatamente visibile all'app.

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 contrassegnare la funzione const , ma potrebbe essere necessario modificare alcuni stati degli oggetti in modo da non comprometterne la corretta 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 mutable suggerisce che ciò non dovrebbe essere fatto.

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

  • Sicurezza del thread. Dichiarare un boost mutabile :: mutex è perfettamente ragionevole.
  • Statistica. Conteggio del 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.

Il 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. Pertanto, se senza "mutabile", non si sarebbe in grado di avere "const" funzioni perché questa variabile dovrà essere modificata in tutte le funzioni disponibili per il 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   è sicuro e ci si aspetta che una variabile membro possa essere modificata all'interno di una const   funzione membro.

mutabile 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.

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

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

I casi che lo uso più frequentemente sono l'inizializzazione lenta di "vecchi dati semplici" senza stato " membri. 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.

La mutevole modifica il significato di const da const bit a bit a 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 alle funzioni dei membri const di modificare i membri mutabili senza utilizzare 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 ho voluto sottolineare che non è solo per la sicurezza dei tipi e che influenza il risultato compilato.

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

Mi sembra un hack anche per la maggior parte del tempo. Utile in pochissime situazioni.

L'esempio classico (come menzionato in altre risposte) e l'unica situazione in cui ho visto la parola chiave mutable utilizzata finora, è per memorizzare nella cache il risultato di un Get metodo, 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 normale).

In generale, le alternative all'uso della parola chiave mutable sono generalmente una variabile statica nel metodo o il trucco const_cast .

Un'altra spiegazione dettagliata è in qui .

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 vorrai modificare l'interfaccia della classe base, quindi devi usare la tua variabile membro mutabile

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.

Uno dei migliori esempi in cui usiamo mutable è, in copia profonda. nel costruttore di copie inviamo const & amp; obj come 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 .

La classe di archiviazione

mutable può essere utilizzata solo su un membro di dati non const non statico di una classe. Un 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 x sebbene faccia parte di un oggetto che è 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.

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 client, utilizzare la parola chiave mutable.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top