The
mutable keyword refers to obscure parts of the C ++ language. At the same time, it can be very useful, or even necessary, if you want to strictly adhere to the const correctness of your code or write lambda functions that can change its state.
A couple of days ago, Eric Smolikowski tweeted:
“I often ask programmers at the interview how well (on a 10-point scale) they know C ++. Usually they answer 8 or 9. And then I ask what is „mutable“. They do not know. :) "')
My impressions of such questions and answers are twofold. On the one hand, asking such questions at an interview is useless, it says almost nothing about the abilities of the interviewee. But, on the other hand, the keyword
mutable is undeservedly forgotten by many programmers, and in fact it can be very useful in some scenarios.
Const-correctness: semantic constancy versus syntactic constancy
When we try to write code that is correct from the point of view of using the concept of constancy, we are confronted with the fact that semantic immutability is not equivalent to syntactic immutability. In other words, we may need to change the state of the object (if implementation details require it), while keeping the state of the object visible from the outside constant.
A change in the internal state may be required for some deep technical reasons and this should not be noticeable to external customers of our class. But our choice is not big - if we use the const keyword when declaring a method, the compiler will not allow us to change an object of this class, even if no one outside the class will notice these changes.
Cached data
A good example would be data caching. Let's take a look at this polygon class:
class Polygon { std::vector<Vertex> vertices; public: Polygon(std::vector<Vertex> vxs = {}) : vertices(std::move(vxs)) {} double area() const { return geometry::calculateArea(vertices); } void add(Vertex const& vertex) { vertices.push_back(vertex); }
Let's assume that geometry :: calculateArea is a very resource-consuming function that we don’t want to call every time we call the area () method. We can calculate the new area when changing the landfill, but in some scenarios this may be as much (or even more) resource-intensive. A good solution in this situation can be the calculation of the area only when it is necessary, with caching the result and clearing the cache in the event of a polygon change.
class Polygon { std::vector<Vertex> vertices; double cachedArea{0}; public:
But hey wait, not so fast! The compiler will not let you turn this trick, because the area () method is marked constant, and for some reason we are trying to change the cachedArea property in it. Remove const from method declaration? But then we will not understand the customers of this class. After all, area () is a simple getter, this function definitely should not change anything in the class. So why is there no const in her ad?
Mutexes
Another example is thread safety using mutexes. The vertex container in the example above is not thread safe. Thus, in a multithreaded application, where different streams will share data from the same polygons, we need to ensure the security of access to this data:
class Polygon { std::vector<Vertex> vertices; std::mutex mutex; public: Polygon(std::vector<Vertex> vxs = {}) : vertices(std::move(vxs)) {} double area() const { std::scoped_lock lock{mutex}; return geometry::calculateArea(vertices); } void add(Vertex const& vertex) { std::scoped_lock lock{mutex}; vertices.push_back(vertex); }
In this case, the compiler will again complain about the area () method, which vigorously promises to be constant, but it itself (that's a scoundrel!) Tries to perform the mutex :: lock () operation, which changes the state of the mutex. That is - we cannot lock a constant mutex.
It turns out that we again cannot make the area () method constant and will have to either abandon thread safety or mislead the clients of our class, getting rid of const in the method declaration. Because of the technical details of the implementation, which have absolutely no relation to the state of the object that is visible from the outside, we have to either give up some of the functionality or mislead the users of the class.
The keyword "mutable" to the rescue
The
mutable keyword exists in the C ++ standard for solving this class of problems. It can be added to the variable members of the class to indicate that this variable can be changed even in a constant context. Using
mutable, the solution to both of the above examples would look like this:
class Polygon { std::vector<Vertex> vertices; mutable double cachedArea{0}; mutable std::mutex mutex; public:
Variable lambda functions
There is one more variant of applying the
mutable keyword and it is connected with saving the state in lambda functions. Usually, the operator of the function call closure is constant. In other words, lambda cannot modify variables captured by value:
int main() { int i = 2; auto ok = [&i](){ ++i; };
But the
mutable keyword can be applied to the entire lambda function, which will make all its variables changeable:
int main() { int i = 2; auto ok = [i, x{22}]() mutable { i++; x+=i; }; }
It should be noted that unlike mutable variables in a class declaration, mutable lambda functions should be used relatively rarely and very carefully. Keeping state between calls to the lambda function can be dangerous and counterintuitive.
findings
mutable is not some dark and dusty corner of C ++ that you will never need. This is a tool that plays its role in clean code, and plays it the better, the more often you use const and the more you try to make your code safe and secure. With the use of
mutable, you can better explain to the compiler where its checks are important and necessary, and where you want to avoid them. All this increases the overall correctness of the code.