08 - mutable
Expensive getters
Consider a situation where there is a class that performs lots of calculations and the result takes some time to compute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class simulation { public: bool set_amount(int value) { if (value < 1) return false; amount = value; return true; } bool set_time(double ms) { if (value < 0.0) return false; time_ms = ms; return true; } bool set_velocity(double value) { velocity = value; return true; } // more setters... simulation_result compute_result() const; // very expensive private: int amount; double time_ms; double velocity; // more data members... }; |
So far everything looks fine:
all fields are private
setters secure invariants
computation function is const-qualified
But there is one problem - we know that data doesn't change often but we often need the computation result. If we call this function over and over, we are continuously repeating the same (expensive) calculations. We could implement some caching so that if data has not changed the class just returns the last result.
We can add a result field to the class to store last result inside but now we have a problem - the const-qualified function to compute the result can not change it!
mutable
members
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class simulation { public: bool set_amount(int value) { if (value < 1) return false; if (value != amount) { amount = value; last_result = std::nullopt; } return true; } bool set_time(double ms) { if (value < 0.0) return false; if (ms != time_ms) { time_ms = ms; last_result = std::nullopt; } return true; } bool set_velocity(double value) { if (value != velocity) { velocity = value; last_result = std::nullopt; } return true; } // more setters... simulation_result get_result() const { if (!last_result) last_result = compute_result(); return last_result.value(); } private: simulation_result compute_result() const; int amount; double velocity; double time_ms; // more data members... mutable std::optional<simulation_result> last_result; }; |
We have moved actual computation to a private function, which is now called when there is no result available. Setters have been modified to reset the result if specified parameter values are different. Now the code outside the class can call get_result
as many times as needed and it will only be computed if something changed.
In practice
In practice mutable
is used very sparingly. It's purpose is to specify that the member does not affect the externally visible state of the class. It should be used when there is a strong desire to const-qualify a member function but specific class implementation (such as result caching in the presented example) requires changes in some members in such function.
The most common use of mutable
is for members that implement synchronization mechanisms such as std::mutex
. For a thread-safe class, a mutex needs to be locked and unlocked in every member function so the only way to preserve const-correctness on the outside is to use mutable std::mutex
member.
Extra technical details
mutable
fields can not beconst
on the top levelmutable
fields can not bestatic
mutable
fields can not be references
1 2 3 4 5 6 7 8 9 |
struct test { mutable int* p1; // ok mutable const int* p2; // ok mutable int* const p3; // error mutable const int* const p4; // error mutable int& r; // error mutable static int s; // error }; |