Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 57 di 93
  • livello avanzato
Indice lezioni

Il qualificatore mutable

Il qualificatore mutable permette di rilassare i vincoli di const-correctness: ecco come e quando è utile usarlo nel linguaggio C++.
Il qualificatore mutable permette di rilassare i vincoli di const-correctness: ecco come e quando è utile usarlo nel linguaggio C++.
Link copiato negli appunti

Il linguaggio C++ contempla la possibilità di definire entità referenziabili in sola lettura (read-only) anche se ciò non le qualifica necessariamente come immutabili.

Questo meccanismo di protezione degli accessi, proprio del C++, viene identificato con il nome di const-correctness e serve a incrementare la resilienza generale della nostra applicazione all'eventuale modifica accidentale di valori che, per vincoli progettuali o di altra natura, si suppone non vengano mai alterati dalle istruzioni del nostro programma, dopo la loro prima inizializzazione.

La parola chiave mutable, usata in sinergia con il qualificatore const, si inserisce proprio in questo contesto semantico, per preservare l'applicabilità della const-correcteness al nostro data model, anche in casi particolari.

A differenza di altri qualificatori polivalenti, mutable può essere applicato ai soli dati membro di una classe. Non è quindi applicabile a metodi o variabili, come invece accade per static o volatile.

Const-correctness

In linea teorica, è facile comprendere che alcune delle informazioni elaborate dal nostro programma rimangono immutate durante l'esecuzione dello stesso. Si pensi, ad esempio, alle costanti matematiche come il pi greco, la cui modifica accidentale potrebbe generare errori di calcolo.

Tuttavia, rispetto ad altri linguaggi come Java o C#, il problema dell'immutabilità in C++ ha radici più profonde, ed è essenzialmente riconducibile al dualismo tra accessibilità al valore e al riferimento di una variabile.

In C++ infatti, la distinzione tra valore e riferimento emerge a livello sintattico, e viene gestita mediante l'uso di costrutti appositi per la copia di un valore e operatori di referenziazione e dereferenziazione per gli indirizzi di memoria. Accedere per valore ad una variabile in C++ significa creare una copia e usarla al posto dell'originale. Ciò è una garanzia implicita di trasparenza referenziale, poiché ogni eventuale modifica si applica solo alla copia; tuttavia, può essere costoso in termini di risorse.

Per aggirare il problema del costo, si può passare una variabile per riferimento, applicando il meccanismo di copia al suo indirizzo. Tuttavia, ciò abilita, di fatto, le modifiche al valore dell'originale, con ripercussioni anche al di fuori del contesto del ricevente.

Il meccanismo di const-correctness unisce il meglio dei due mondi, poiché tramite l'uso del qualificatore const offre uno strumento sintattico per definire la semantica di accesso al riferimento di una variabile e prevenire così l'eventualità che essa venga modificata in modo inatteso.

Problema risolto, dunque?

Non del tutto. La const-correctness è una strada a senso unico, che restringe il numero di API invocabili su un'istanza const-qualified a quelle che ne soddisfano il requisito di immutabilità, come ad esempio i metodi const.

Una limitazione di questo tipo, per quanto fondata su una progettazione attenta e ragionata, può risultare invalidante, al punto da spingere alcuni sviluppatori a rigettare in toto l'applicazione dei criteri di const-correctness nei loro programmi.

Per evitare questa scelta drastica, il qualificatore mutable consente di rilassare i vincoli di const-correctness selettivamente per i dati membro di una classe che possono essere soggetti a modifiche anche quando lo stato osservabile dell'istanza cui appartengono non cambia.

Mutable e la const-correctness

Come esempio dell'effetto del qualificatore mutable, si consideri il listato seguente nel quale esso viene applicato ad un dato membro della classe Dummy.

#include <iostream>
class Dummy
{
public:
Dummy() {
val = 0;
mutableVal = 0;
}
void doSomething() const {
val++; // errore! val non è modificabile in un metodo const
std::cout << ++mutableVal << '\n'; // ok, possiamo alterare un membro mutable
}
protected:
int val;
mutable int mutableVal;
};
int main()
{
Dummy d;
d.doSomething();
return 0;
}

Come conseguenza mutableVal, a differenza di altri dati membro non mutabili, risulta accessibile e modificabile anche nel contesto del metodo doSomething() che è qualificato con const.

Nella pratica, l'uso di mutable risulta particolarmente indicato in tutti quei casi in cui lo stato osservabile di una classe è composto da un sottoinsieme dei suoi dati membri. Da un punto di vista logico è questo sottoinsieme che vogliamo preservare applicando i criteri di const-correctness.

Tuttavia, per ragioni implementative, una classe può contenere anche dati membro che ne regolano il funzionamento interno senza inficiarne lo stato nell'ottica del modello dati che stiamo rappresentando. In alcuni casi quindi, può rendersi necessario distinguere i membri mutabili sempre e comunque da quelli che invece devono conformarsi alle nostre politiche di limitazione di accesso.

In generale, i casi tipici in cui il qualificatore mutable risulta utile sono i seguenti:

  • thread-safety: nel caso di applicazioni multiprogrammate, può rendersi necessario l'uso di mutex per implementare sezioni critiche in metodi const-qualified. Il mutex è tipicamente un membro di classe mutable che può essere alterato mediante operazioni di locking/unlocking anche in contesti in cui vogliamo garantire const-correctness.
  • data caching: a volte anche un metodo che non altera lo stato osservabile di una classe può comunque essere oneroso in termini computazionali. In questo caso, per ragioni di ottimizzazione, può essere necessario immagazzinare un risultato già calcolato fin tanto che esso rimane valido e riusabile in una dato membro qualificato con mutable.

In questi ed altri scenari, quindi, l'uso di mutable risulta essenziale per preservare la const-correctness del nostro modello dati. Le uniche limitazioni riguardano la sua incompatibilità con i qualificatori static e const, ed il fatto che esso non può essere applicato a dati membro reference.

Lo standard c++11 ha esteso l'applicabilità del qualificatore mutable alle espressioni lambda in cui, come vedremo meglio nel seguito, esso è usato per modificare esplicitamente il meccanismo di cattura delle variabili ed aggirare il vincolo di trasparenza referenziale.

Ti consigliamo anche