C++ 中的 mutable 关键字和数据缓存






4.75/5 (3投票s)
一个表示有效/无效数据的类以及 mutable 关键字的使用
引言
本文将介绍 C++ 关键字 mutable,并深入探讨如何使用 mutable 进行数据缓存。 为了说明这一点,我将举一个来自 Python 的例子。 如果我有一个类的属性计算成本很高,我想缓存它,并且只在必要时重新计算。
#============================================================================== 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
在前面的例子中,我使用了 Python 的动态类型来将数据和一个布尔值本质上结合在一起。 因此,如果它是 None
,它将被计算,否则将返回缓存的值。
已验证的类
现在我的目标是在 C++ 中复制它。
C++ 是一种静态类型 语言,因此我们不能使用完全相同的方法。 所以我所做的是创建一个类来封装这个想法。 由于数据的类型与该类无关,所以我将其设为一个模板类。 这是我称为 Validated 的类的公共 API。
//=============================================================================
template <typename T>
class Validated {
public:
Validated();
// invalid constructor
Validated(const T& data);
// valid constructor
bool valid() const;
// is this valid or not?
void invalidate();
// makes the class invalid again, so call in a function which will change the
// cached value.
void set_data(const T& data);
// set the data, makes the instance valid.
operator T();
operator T() const;
// implicit conversions to the template type.
这决定了类的行为,因此如果它具有数据值,则它是有效的,如果未设置数据值,则它是无效的。 它还(异常地)包含一个到数据类型的隐式类型转换器。 这是为了它可以自然地被视为它正在验证的数据。 这通常使用显式函数完成,例如标准字符串上的 c_str()
函数(另请参见 More Effective C++ 中的第 5 项),但我认为 Validated 类只是数据的包装器,因此它应该本地处理其操作。
这背后的代码非常简单,可以在这里看到。
这种方法也可以通过从 Validated 基类私有继承(实现继承)来实现。 我认为这是一个非常通用的概念,因此应该是一个模板,这也意味着它可以与现有类和内置类一起使用。
使用 Validated
现在开始使用这个类和关键字 mutable。 我的例子将是一个三角形并计算它的面积。 因此,这是我的 Triangle 类的 API,以及一个 Point 结构来保存坐标。
//=============================================================================
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);
// Constructor, points should be entered anti-clockwise.
void set_first_point(const Point& point);
// Sets the first point
double area() const;
// Returns the area
这是一个非常简单的类,但它的实现非常有趣。 以下是 Triangle 的私有成员。
private:
Point m_point_1;
Point m_point_2;
Point m_point_3;
mutable Validated<double> m_area;
// this is mutable as it is used as a cache in area() which is a logically
// const method
这就是这篇文章的重点所在,mutable Validated double。 这是 area()
函数的一个可能的实现。
//=============================================================================
double Triangle::area() const
//
//D Returns the area
//
{
// work out the area using 1/2 ab sin(C)
// a is the length between point 1 and point 2
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)
);
// b is the length between point 2 and point 3
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)
);
// c is the length between point 3 and point 1
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)
);
// use the cosine rule cos(C) = (a^2 + b^2 - c^2) / 2ab
double cos_C = (a*a + b*b - c*c) / (2 * a * b);
return 0.5 * a * b * sin(acos(cos_C);
}
这不是一个计算成本很高的函数,但假设我们经常调用它,因此我们决定将结果缓存在成员变量 m_area
中。 我们有两个选择,因为我们将设置一个成员变量,我们可以更改该方法使其不是 const。 这工作正常,但计算面积在逻辑上不会以任何方式改变三角形,因此我们称它为逻辑 const。 您正在缓存面积的事实是一个实现细节,不应反映在类的公共 API 中。 所以这就是为什么使用关键字 mutable。 这意味着您可以在 const 方法中更改该变量。 这就是我将 m_area
变量声明为 mutable 的原因。 将其与 Validated 类结合起来,该函数变为如下所示。
//=============================================================================
double Triangle::area() const
//
//D Returns the area, first time it works it out it caches the result.
//
{
if (!m_area.valid()) {
// work out the area using 1/2 ab sin(C)
// a is the length between point 1 and point 2
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)
);
// b is the length between point 2 and point 3
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)
);
// c is the length between point 3 and point 1
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)
);
// use the cosine rule cos(C) = (a^2 + b^2 - c^2) / 2ab
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;
}
因此,Validated 类包装器用于缓存值,并且该成员是 mutable 的,以便该方法可以是 const 的。
在BitBucket 存储库 中,有一个可执行文件显示正在使用缓存的值。 它还显示了在 Triangle 类的非 const 方法中使用的 Validated::invalidate()
方法。
使用代码
附加的 .zip 是可以使用 make 编译的项目文件夹。 该代码也可以从 https://bitbucket.org/davidcorne/mutable 下载。
历史
- 2012年10月26日 编写。
- 2012年10月26日 附加 .zip 项目文件。