In embedded systems, many driver APIs are marked const because calling them does not modify the device’s logical or externally observable state.
However, such functions may still update internal bookkeeping values, such as access counters or debug metrics.
These internal changes are allowed because they do not change the logical state of the object (e.g., hardware configuration, register contents, outputs). They only update metadata that is invisible to external users of the object.
Your task is to modify the class so that the logAccess() function can update an internal counter, even though both the function and the object are declared const.
What you must do:
logAccess() N times on a const object.logAccess() must successfully increment the counter, even though it is a const function.N calls, getCount() must return N.N = 0, no calls should be recorded, and the output must be 0.Although logAccess() modifies a variable, it only updates internal bookkeeping, not the logical external state, which makes this valid inside a const function.
Important:
Do not use incorrect approaches such as:
const_caststaticconst keywordThese break const-correctness.
There is a correct and safe C++ mechanism to allow internal updates.
Example
Input:
3
Output:
3
Constraints:
logAccess() as a const function.main() const.
In C++, a const member function (e.g., void read() const) promises not to modify any member variables of the object. The compiler enforces this strictly.
However, sometimes you need to modify a variable that does not affect the logical state of the object, such as a mutex lock, a usage counter, or a cached value.
The mutable keyword allows a specific member variable to be modified even inside a const function.
1. Basic Usage (The Cache Example)
Imagine a sensor class where reading is const (logical state doesn't change), but you want to update a "last read timestamp" for debugging.
class Sensor {
private:
int pin;
mutable int last_access_time; // Can be changed in const functions
public:
Sensor(int p) : pin(p), last_access_time(0) {}
int read() const {
// We are reading hardware, so logical state is const.
// But we want to update the debug timestamp.
last_access_time = get_system_time(); // ✅ Allowed because of 'mutable'
return hardware_read(pin);
}
};2. Thread Safety (Mutexes)
This is the most common real-world use case. Locking a mutex changes the mutex's internal state, but it doesn't change the logical state of the data it protects.
class DataStore {
mutable std::mutex mtx; // Mutex must be mutable
int data;
public:
int get_data() const {
std::lock_guard<std::mutex> lock(mtx); // ✅ Modifies mtx, but that's okay
return data;
}
};| Bitwise Const (Default) | Logical Const (With mutable) |
|---|---|
| Compiler checks strictly. | Developer promises logical consistency. |
| No bits in the object can change. | Some bits (caches/locks) change, but the object "looks" the same. |
| Good for simple data structs. | Essential for complex objects with internal management. |
1. Thread-Safe Getters
In firmware, you often have get_status() functions that must be thread-safe. To make them thread-safe, you need to lock a mutex or disable interrupts. Since get_status() should logically be const, you mark the mutex/lock as mutable to allow locking without removing the const qualifier from the API.
2. Lazy Initialization / Caching
Embedded systems often delay expensive calculations until needed.
class Config {
mutable int cached_crc;
mutable bool crc_valid = false;
public:
int get_crc() const {
if (!crc_valid) {
cached_crc = calculate_expensive_crc(); // Modifies state lazily
crc_valid = true;
}
return cached_crc;
}
};| Pitfall | Details |
|---|---|
| ❌ Overuse | Don't use mutable just to cheat the compiler because you're too lazy to fix your design. Only use it for things that truly don't affect the object's "value." |
| ❌ Thread Safety Confusion | Just because a variable is mutable doesn't make it thread-safe. You still need locks. |
| ✅ Debug Counters | It is excellent for internal debug counters (e.g., mutable int access_count;) that track how many times a const function was called without changing the function signature. |