简单地说,因为赋值有两个方面:左侧和右侧。
非常量版本:T& operator() (unsigned i, unsigned j); 主要用于赋值的左侧(即用作赋值的目标)。
const 版本:T const& operator() (unsigned i, unsigned j) const; 专门用于赋值的右侧。
请注意这里的措辞差异:const 版本只能用于赋值的右侧,而非 const 版本可以用于任何一边。然而,如果你有一个const-qualified 对象,你只能调用 const-qualified 成员函数,所以在这种情况下它根本不能使用。这正是您(至少通常)想要的——它可以防止修改您说过不应该修改的对象(通过 const 限定它)。
就mutate 而言,它通常仅用于在其逻辑 状态和位 状态之间存在差异的对象。一个常见的例子是一个懒惰地做一些(通常是昂贵的)计算的类。为了避免重新计算该值,它会在计算后保存该值:
class numbers {
std::vector<double> values;
mutable double expensive_value;
bool valid;
public:
numbers() : valid(false) {}
double expensive_computation() const {
if (valid) return expensive_value;
// compute expensive_value here, and set `valid` to true
}
};
因此,expensive_computation 的结果完全取决于values 中的值。如果您不关心速度,您可以在每次用户调用expensive_computation 时重新计算该值。在一个 const 对象上重复调用它总是会产生相同的结果——所以调用它一次后,我们假设它可能会被再次调用,并且为了避免重复执行相同的昂贵计算,我们只需将值保存到 @987654330 @。然后,如果用户再次请求它,我们只需返回该值。
换句话说,从逻辑的角度来看,即使/如果我们修改了expensive_value,对象仍然是const。对象的可见状态不会改变。我们所做的只是让它更快地完成 const 的事情。
为了使其正常工作,我们还希望在用户修改values 的内容时将valid 设置回false。例如:
void numbers::add_value(double new_val) {
values.push_back(new_val);
valid = false;
}
在某些情况下,我们可能还需要一个中间级别的有效性——我们可能能够通过(例如)确切地知道哪些数字已添加到 values 来更快地重新计算 expensive_value,而不是只是有一个布尔值来说明它当前是否有效。
我可能应该补充一点,C++11 对const 和mutable 都增加了一些新要求。长话短说,在大多数情况下,您需要确保 const 和/或 mutable 的任何内容也是线程安全的。您可能想在此观看 Herb Sutter's video。然而,我确实觉得有必要补充一点,我认为他关于mutable 的结论可能有点夸大了(但我宁愿你自己观看并决定,而不是相信我的话它)。