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