Parola chiave mutable
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 30 di 104.
Hai imparato che le funzioni membro const non possono modificare alcuna variabile membro. Ma a volte devi modificare un membro che non influisce sullo stato logico dell'oggetto, come una cache o un contatore di accessi. La parola chiave mutable costituisce un'eccezione alla restrizione const.
Un membro modificabile può essere modificato anche all'interno di una funzione membro const:
class DataFetcher {
std::string data;
mutable int accessCount; // Può essere modificato nelle funzioni const
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Consentito perché accessCount è mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Senza mutable, incrementare accessCount all'interno della funzione const getData() causerebbe un errore del compilatore. La parola chiave mutable dice al compilatore: "Questo membro può cambiare anche quando l'oggetto è considerato const."
I casi d’uso più comuni di mutabile includono la memorizzazione nella cache dei valori calcolati, il monitoraggio delle statistiche di accesso e primitive di sincronizzazione come i mutex. In ciascun caso, la modifica non cambia ciò che l’oggetto rappresenta logicamente: un valore memorizzato nella cache è solo un’ottimizzazione e un contatore degli accessi è un metadato sull’utilizzo.
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};Usa mutable con parsimonia: dovrebbe essere applicato solo ai membri che non influiscono sullo stato osservabile dell'oggetto.
Sfida
FacileCreiamo un visualizzatore di documenti che tenga traccia di quante volte è stato visualizzato un documento, senza compromettere la correttezza rispetto a const dei nostri metodi di sola lettura. Questo è un caso d’uso perfetto per la parola chiave mutable.
Creerai due file per organizzare il codice:
Document.h: definisci una classeDocumentche memorizza il contenuto e tiene segretamente traccia delle statistiche di visualizzazione. La classe deve includere:- Membri privati:
title(stringa),content(stringa) e unmutable int viewCountche può essere modificato anche nei metodi const - Un costruttore che accetta un titolo e un contenuto e inizializza
viewCounta0 - Un metodo const
getTitle()che restituisce il titolo - Un metodo const
getContent()che incrementaviewCount(consentito perché è mutable) e restituisce il contenuto - Un metodo const
getViewCount()che restituisce quante volte è stato consultato il contenuto - Un metodo const
getPreview()che restituisce i primi 20 caratteri del contenuto seguiti da"...". NON deve incrementare il conteggio delle visualizzazioni, perché si tratta solo di un’anteprima
- Membri privati:
main.cpp: mostra come mutable consenta di tenere traccia delle statistiche di accesso mantenendo i metodi logicamente const. Leggi da input il titolo e il contenuto di un documento (ciascuno su una riga separata), poi:- Crea un
Documentcon i valori di input - Stampa
"Title: <title>" - Stampa
"Preview: <preview>" - Stampa
"Views after preview: <count>" - Accedi al contenuto completo usando
getContent()e stampa"Content: <content>" - Stampa
"Views after first read: <count>" - Accedi di nuovo al contenuto e stampa
"Content: <content>" - Stampa
"Views after second read: <count>" - Crea una funzione di supporto
void displayDocument(const Document& doc)che stampa"[Const Access] <title>: <content>". Questo dimostra che i nostri metodi const funzionano con riferimenti const - Chiama
displayDocument()passando il tuo documento - Stampa
"Final view count: <count>"
- Crea un
Il conteggio delle visualizzazioni dovrebbe essere 0 dopo l’anteprima (dato che le anteprime non contano come visualizzazioni complete), poi dovrebbe aumentare ogni volta che viene chiamato getContent(). La parola chiave mutable lo rende possibile: senza di essa, non potresti modificare viewCount all’interno di metodi const come getContent().
Se il contenuto è più corto di 20 caratteri, getPreview() dovrebbe restituire l’intero contenuto seguito da "...".
Provalo tu
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: Implementa la funzione displayDocument
// Deve stampare: "[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: Implementa questa funzione
}
int main() {
// Leggi l'input
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: Crea un Document con i valori di input
// TODO: Stampa "Title: <title>"
// TODO: Stampa "Preview: <preview>"
// TODO: Stampa "Views after preview: <count>"
// TODO: Accedi al contenuto completo e stampa "Content: <content>"
// TODO: Stampa "Views after first read: <count>"
// TODO: Accedi nuovamente al contenuto e stampa "Content: <content>"
// TODO: Stampa "Views after second read: <count>"
// TODO: Chiama displayDocument() con il tuo documento
// TODO: Stampa "Final view count: <count>"
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online