This blog post is going to be about the C++ keyword mutable
, it is also going to go into using mutable
for data caching. To motivate this, I am going to give an example from Python. If I have a class with a property which is expensive to compute, I want to cache it and only recalculate it when necessary.
#==============================================================================
class DataClass(object):
def __init__(self):
self.__data = None
@property
def data(self):
if (self.__data is None):
# work out self.__data
return self.__data
In the preceding example, I have used the dynamic typing of Python to essentially combine data and a boolean. So if it is None
, it will be calculated and if not, the cached value will be returned.
Now my goal is to replicate this in C++.
The full code to this post (and more!) is available from my bit bucket at mutable.
C++ is a statically typed language so we cannot use the exact same approach here. So what I have done is make a class to encapsulate this idea. As the type of data is irrelevant to this class, I have made it a template class. Here is the public
API of the class I have called Validated
.
template <typename T>
class Validated {
public:
Validated();
Validated(const T& data);
bool valid() const;
void invalidate();
void set_data(const T& data);
operator T();
operator T() const;
This dictates the behaviour of the class so if it has a data value, then it is valid and if one has not been set then it is invalid. It also (unusually) contains an implicit type converter to the data type. This is so it can be naturally treated as the data it is validating. This is normally done using an explicit function, for example the c_str() function on a standard string
(also see item 5 in More Effective C++), however I feel that the Validated
class is just a wrapper around the data so it should natively handle its operations.
The code behind this is quite straight forward and can be seen here.
This approach can also be accomplished by privately inheriting (implementation inheritance) from a Validated base class. I think that this is a very generic concept and so should be a template.
Now for the use of this class and the keyword mutable
. My example is going to be a triangle and calculating its area. So here is the API for my Triangle
class, and a Point struct
to hold the coordinates.
struct Point {
Point(double new_x, double new_y) {
x = new_x;
y = new_y;
}
double x;
double y;
};
class Triangle {
public:
Triangle(const Point& point_1, const Point& point_2, const Point& point_3);
void set_first_point(const Point& point);
double area() const;
This is quite a straightforward class but it is the implementation which is interesting here. Here are the private
members of Triangle
.
private:
Point m_point_1;
Point m_point_2;
Point m_point_3;
mutable Validated<double> m_area;
This is where the point of this post comes in, the mutable Validated
double. Here is a possible implementation of the area()
function.
double Triangle::area() const
{
double a = sqrt(
(m_point_2.x - m_point_1.x) * (m_point_2.x - m_point_1.x) +
(m_point_2.y - m_point_1.y) * (m_point_2.y - m_point_1.y)
);
double b = sqrt(
(m_point_3.x - m_point_2.x) * (m_point_3.x - m_point_2.x) +
(m_point_3.y - m_point_2.y) * (m_point_3.y - m_point_2.y)
);
double c = sqrt(
(m_point_1.x - m_point_3.x) * (m_point_1.x - m_point_3.x) +
(m_point_1.y - m_point_3.y) * (m_point_1.y - m_point_3.y)
);
double cos_C = (a*a + b*b - c*c) / (2 * a * b);
return 0.5 * a * b * sin(acos(cos_C);
}
This is not that computationally expensive a function but suppose we are calling it a lot, so we decide to cache the result in a member variable m_area
. We have two choices here as we are going to be setting a member variable we could change the method to not be const
. This works fine but working out the area is logically not going to change the triangle in any way so we call it logically const
. The fact that you are caching the area is an implementation detail that should not be reflected in the public
API of the class. So this is why the keyword mutable
is used. It means you can change that variable in a const
method. This is why I declared the m_area
variable as mutable. Combining this with the Validated
class, the function becomes this:
double Triangle::area() const
{
if (!m_area.valid()) {
double a = sqrt(
(m_point_2.x - m_point_1.x) * (m_point_2.x - m_point_1.x) +
(m_point_2.y - m_point_1.y) * (m_point_2.y - m_point_1.y)
);
double b = sqrt(
(m_point_3.x - m_point_2.x) * (m_point_3.x - m_point_2.x) +
(m_point_3.y - m_point_2.y) * (m_point_3.y - m_point_2.y)
);
double c = sqrt(
(m_point_1.x - m_point_3.x) * (m_point_1.x - m_point_3.x) +
(m_point_1.y - m_point_3.y) * (m_point_1.y - m_point_3.y)
);
double cos_C = (a*a + b*b - c*c) / (2 * a * b);
m_area.set_data(0.5 * a * b * sin(acos(cos_C)));
}
return m_area;
}
So the Validated
class wrapper is used to cache the value and the member is mutable so that the method can be const
.
In the BitBucket repository, there is an executable which shows that the cached value is being used. It also shows the Validated::invalidate()
method being used in a non-const
method on the Triangle
class.
If you want to know more about how to NOT use mutable
, this is a very good article on it which shows some common but incorrect examples of use.
Thanks for reading and putting up with the sporadic and long posts.