Słowo kluczowe mutable
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 30 z 104.
Wiesz już, że funkcje składowe const nie mogą modyfikować żadnych zmiennych składowych. Czasami jednak trzeba zmienić składową, która nie wpływa na logiczny stan obiektu — na przykład pamięć podręczną lub licznik dostępu. Słowo kluczowe mutable stanowi wyjątek od ograniczenia const.
Składową mutable można modyfikować nawet wewnątrz funkcji składowej const:
class DataFetcher {
std::string data;
mutable int accessCount; // Może być modyfikowane w funkcjach const
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Dozwolone, ponieważ accessCount jest mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Bez mutable zwiększenie accessCount wewnątrz funkcji const getData() spowodowałoby błąd kompilatora. Słowo kluczowe mutable informuje kompilator: „Ten element może się zmieniać, nawet gdy obiekt jest uznawany za const”.
Typowe zastosowania słowa mutable obejmują buforowanie obliczonych wartości, śledzenie statystyk dostępu oraz prymitywy synchronizacyjne, takie jak muteksy. W każdym przypadku modyfikacja nie zmienia tego, co obiekt reprezentuje logicznie — buforowana wartość jest tylko optymalizacją, a licznik dostępu to metadane dotyczące użycia.
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;
}
};Używaj mutable oszczędnie — należy stosować je tylko do składowych, które nie wpływają na obserwowalny stan obiektu.
Wyzwanie
ŁatwyStwórzmy przeglądarkę dokumentów, która śledzi, ile razy wyświetlono dokument, nie naruszając poprawności stałości naszych metod tylko do odczytu. To doskonały przykład zastosowania słowa kluczowego mutable.
Utworzysz dwa pliki, aby uporządkować swój kod:
Document.h: Zdefiniuj klasęDocument, która przechowuje treść i dyskretnie śledzi statystyki wyświetleń. Twoja klasa powinna zawierać:- Pola prywatne:
title(string),content(string) orazmutable int viewCount, które można modyfikować nawet w metodach const - Konstruktor, który przyjmuje tytuł i treść, inicjalizując
viewCountwartością0 - Metodę
getTitle()const, która zwraca tytuł - Metodę
getContent()const, która zwiększaviewCount(jest to dozwolone, ponieważ pole jest mutable) i zwraca treść - Metodę
getViewCount()const, która zwraca liczbę dostępów do treści - Metodę
getPreview()const, która zwraca pierwsze 20 znaków treści, po których następuje"...". Nie powinna ona zwiększać licznika wyświetleń, ponieważ jest to tylko podgląd
- Pola prywatne:
main.cpp: Pokaż, jak mutable pozwala śledzić statystyki dostępu przy zachowaniu logicznej stałości metod. Wczytaj tytuł i treść dokumentu z wejścia (każde w osobnym wierszu), a następnie:- Utwórz obiekt
Documentz wczytanymi wartościami - Wypisz
"Title: <title>" - Wypisz
"Preview: <preview>" - Wypisz
"Views after preview: <count>" - Pobierz całą treść za pomocą
getContent()i wypisz"Content: <content>" - Wypisz
"Views after first read: <count>" - Pobierz treść ponownie i wypisz
"Content: <content>" - Wypisz
"Views after second read: <count>" - Utwórz funkcję pomocniczą
void displayDocument(const Document& doc), która wypisuje"[Const Access] <title>: <content>". Dowodzi to, że nasze metody const działają z referencjami const - Wywołaj
displayDocument(), przekazując jej swój dokument - Wypisz
"Final view count: <count>"
- Utwórz obiekt
Licznik wyświetleń powinien wynosić 0 po wyświetleniu podglądu (ponieważ podglądy nie są liczone jako pełne wyświetlenia), a następnie zwiększać się za każdym razem, gdy wywołana zostanie metoda getContent(). Słowo kluczowe mutable to umożliwia — bez niego nie można byłoby modyfikować viewCount wewnątrz metod const, takich jak getContent().
Jeśli treść ma mniej niż 20 znaków, metoda getPreview() powinna zwrócić całą treść, po której następuje "...".
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: Zaimplementuj funkcję displayDocument
// Powinna wypisać: "[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: Zaimplementuj tę funkcję
}
int main() {
// Wczytaj dane wejściowe
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: Utwórz Document z wartościami wejściowymi
// TODO: Wypisz "Title: <title>"
// TODO: Wypisz "Preview: <preview>"
// TODO: Wypisz "Views after preview: <count>"
// TODO: Uzyskaj dostęp do pełnej zawartości i wypisz "Content: <content>"
// TODO: Wypisz "Views after first read: <count>"
// TODO: Ponownie uzyskaj dostęp do zawartości i wypisz "Content: <content>"
// TODO: Wypisz "Views after second read: <count>"
// TODO: Wywołaj displayDocument() ze swoim dokumentem
// TODO: Wypisz "Final view count: <count>"
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online