C++98corebeginner
Mutable Keyword
C++98 · The Foundation · Compiler flag: -std=c++98
Explanation
Allows a data member to be modified even inside a `const` member function.
Real-World Scenario
Cache a computed value inside a `const` getter to avoid redundant calculations.
Quick Snippet
snippet.cpp
class Data { mutable int cache; public: int get() const { cache = 42; return cache; } };Full Example
mutable.cpp
// CodeNebula - C++98 Example: mutable Keyword
// Compile: g++ -std=c++98 -Wall -Wextra -o example mutable.cpp
// The 'mutable' keyword allows a class member to be modified even when
// accessed through a const reference or inside a const member function.
// This is useful for:
// - Caching/memoization (lazy computation)
// - Mutexes and synchronization primitives
// - Debug counters and access tracking
// - Internal bookkeeping that doesn't affect logical constness
#include <iostream>
#include <string>
#include <cmath>
// ------------------------------------------------------------------
// 1. Caching pattern: expensive computation cached in mutable member
// ------------------------------------------------------------------
class Circle {
private:
double radius;
mutable double cachedArea; // Can be modified in const methods
mutable bool areaCacheValid; // Tracks if cache is up to date
public:
Circle(double r) : radius(r), cachedArea(0.0), areaCacheValid(false) {}
void setRadius(double r) {
radius = r;
areaCacheValid = false; // Invalidate cache when state changes
}
double getRadius() const {
return radius;
}
// This is a const method, but it modifies mutable members
double getArea() const {
if (!areaCacheValid) {
std::cout << " (computing area...)" << std::endl;
cachedArea = 3.14159265 * radius * radius; // OK: mutable
areaCacheValid = true; // OK: mutable
} else {
std::cout << " (returning cached area)" << std::endl;
}
return cachedArea;
}
};
// ------------------------------------------------------------------
// 2. Access counter: track how many times data is read
// ------------------------------------------------------------------
class SecureData {
private:
std::string data;
mutable int accessCount; // Tracks reads without affecting const
public:
SecureData(const std::string& d) : data(d), accessCount(0) {}
// const method that modifies the mutable counter
std::string getData() const {
++accessCount; // OK: mutable member
return data;
}
int getAccessCount() const {
return accessCount;
}
void resetAccessCount() {
accessCount = 0;
}
};
// ------------------------------------------------------------------
// 3. Lazy initialization pattern
// ------------------------------------------------------------------
class Config {
private:
std::string filename;
mutable std::string cachedContent;
mutable bool loaded;
public:
Config(const std::string& fname)
: filename(fname), cachedContent(""), loaded(false) {}
// Const method that lazily "loads" data on first access
const std::string& getContent() const {
if (!loaded) {
std::cout << " (loading config from \"" << filename << "\"...)" << std::endl;
// Simulate loading from file
cachedContent = "key1=value1;key2=value2;key3=value3";
loaded = true;
}
return cachedContent;
}
// Check if data has been loaded
bool isLoaded() const {
return loaded;
}
};
// ------------------------------------------------------------------
// 4. Statistics tracker in a const-correct container
// ------------------------------------------------------------------
class DataStore {
private:
int values[10];
int count;
mutable int lookupCount; // How many times find() was called
mutable int lastFoundIndex; // Last successful search result
public:
DataStore() : count(0), lookupCount(0), lastFoundIndex(-1) {}
void add(int value) {
if (count < 10) {
values[count++] = value;
}
}
// const method that updates mutable diagnostic members
int find(int value) const {
++lookupCount; // OK: mutable
for (int i = 0; i < count; ++i) {
if (values[i] == value) {
lastFoundIndex = i; // OK: mutable
return i;
}
}
lastFoundIndex = -1;
return -1;
}
int getLookupCount() const { return lookupCount; }
int getLastFoundIndex() const { return lastFoundIndex; }
void print() const {
std::cout << " DataStore [";
for (int i = 0; i < count; ++i) {
std::cout << values[i];
if (i < count - 1) std::cout << ", ";
}
std::cout << "]" << std::endl;
}
};
// ------------------------------------------------------------------
// 5. Demonstrating mutable with const objects and references
// ------------------------------------------------------------------
void useConstCircle(const Circle& c) {
// Even though 'c' is const, getArea() can update the mutable cache
std::cout << " Area = " << c.getArea() << std::endl;
std::cout << " Area = " << c.getArea() << std::endl; // Should use cache
}
void useConstSecureData(const SecureData& sd) {
// Reading data through a const reference still increments the counter
std::cout << " Data: " << sd.getData() << std::endl;
std::cout << " Data: " << sd.getData() << std::endl;
std::cout << " Access count: " << sd.getAccessCount() << std::endl;
}
int main() {
std::cout << "=== C++98 mutable Keyword ===" << std::endl;
// --- 1. Caching pattern ---
std::cout << "\n--- 1. Caching (Circle::getArea) ---" << std::endl;
Circle circle(5.0);
std::cout << "First call (computes):" << std::endl;
std::cout << " Area = " << circle.getArea() << std::endl;
// Expected: (computing area...) then 78.5398
std::cout << "Second call (cached):" << std::endl;
std::cout << " Area = " << circle.getArea() << std::endl;
// Expected: (returning cached area) then 78.5398
std::cout << "After setRadius(10):" << std::endl;
circle.setRadius(10.0);
std::cout << " Area = " << circle.getArea() << std::endl;
// Expected: (computing area...) then 314.159 (cache invalidated)
std::cout << "Again (cached):" << std::endl;
std::cout << " Area = " << circle.getArea() << std::endl;
// Expected: (returning cached area)
// --- 2. With const reference ---
std::cout << "\n--- 2. Mutable through const reference ---" << std::endl;
const Circle constCircle(3.0);
std::cout << "Calling getArea() on a const Circle:" << std::endl;
useConstCircle(constCircle);
// First call computes, second call returns cached
// --- 3. Access counter ---
std::cout << "\n--- 3. Access counter (SecureData) ---" << std::endl;
SecureData secret("TOP_SECRET_DATA");
std::cout << "Via const reference:" << std::endl;
useConstSecureData(secret);
// Expected: Access count: 2
std::cout << " One more read: " << secret.getData() << std::endl;
std::cout << " Total access count: " << secret.getAccessCount() << std::endl;
// Expected: 3
secret.resetAccessCount();
std::cout << " After reset: " << secret.getAccessCount() << std::endl;
// Expected: 0
// --- 4. Lazy initialization ---
std::cout << "\n--- 4. Lazy initialization (Config) ---" << std::endl;
const Config cfg("settings.ini");
std::cout << " Loaded? " << (cfg.isLoaded() ? "yes" : "no") << std::endl;
// Expected: no
std::cout << " Content: " << cfg.getContent() << std::endl;
// Expected: (loading...) then the content
std::cout << " Loaded? " << (cfg.isLoaded() ? "yes" : "no") << std::endl;
// Expected: yes
std::cout << " Content: " << cfg.getContent() << std::endl;
// Expected: returns immediately (no loading message)
// --- 5. Diagnostic counters ---
std::cout << "\n--- 5. Diagnostic counters (DataStore) ---" << std::endl;
DataStore store;
store.add(10);
store.add(20);
store.add(30);
store.add(40);
store.add(50);
store.print();
// Use a const reference
const DataStore& cstore = store;
int idx = cstore.find(30);
std::cout << " find(30): index=" << idx << std::endl;
// Expected: 2
idx = cstore.find(99);
std::cout << " find(99): index=" << idx << std::endl;
// Expected: -1
idx = cstore.find(50);
std::cout << " find(50): index=" << idx << std::endl;
// Expected: 4
std::cout << " Total lookups: " << cstore.getLookupCount() << std::endl;
// Expected: 3
std::cout << " Last found index: " << cstore.getLastFoundIndex() << std::endl;
// Expected: 4
// --- Summary ---
std::cout << "\n--- Summary ---" << std::endl;
std::cout << "mutable allows modifying a member inside a const method." << std::endl;
std::cout << "Use it for: caching, access counters, lazy init, mutexes." << std::endl;
std::cout << "It preserves 'logical constness' while allowing" << std::endl;
std::cout << "internal state changes that don't affect the object's" << std::endl;
std::cout << "observable behavior." << std::endl;
return 0;
}
/*
Expected Output:
=== C++98 mutable Keyword ===
--- 1. Caching (Circle::getArea) ---
First call (computes):
(computing area...)
Area = 78.5398
Second call (cached):
(returning cached area)
Area = 78.5398
After setRadius(10):
(computing area...)
Area = 314.159
Again (cached):
(returning cached area)
Area = 314.159
--- 2. Mutable through const reference ---
Calling getArea() on a const Circle:
(computing area...)
Area = 28.2743
(returning cached area)
Area = 28.2743
--- 3. Access counter (SecureData) ---
Via const reference:
Data: TOP_SECRET_DATA
Data: TOP_SECRET_DATA
Access count: 2
One more read: TOP_SECRET_DATA
Total access count: 3
After reset: 0
--- 4. Lazy initialization (Config) ---
Loaded? no
(loading config from "settings.ini"...)
Content: key1=value1;key2=value2;key3=value3
Loaded? yes
Content: key1=value1;key2=value2;key3=value3
--- 5. Diagnostic counters (DataStore) ---
DataStore [10, 20, 30, 40, 50]
find(30): index=2
find(99): index=-1
find(50): index=4
Total lookups: 3
Last found index: 4
--- Summary ---
mutable allows modifying a member inside a const method.
Use it for: caching, access counters, lazy init, mutexes.
It preserves 'logical constness' while allowing
internal state changes that don't affect the object's
observable behavior.
*/
Run command
g++ -std=c++98 -Wall -Wextra -o example mutable.cpp