C++ 的类型系统中提供了两个与安全性相关的限定符:const 和 volatile,它们在许多方面存在关联。const 限定符表示被限定的对象在当前作用域内被视为不可变:
const int N = 3; // 全局常数
class X {
int n; // 注意:不是const
public:
X(int n) : n{ n } {
}
int g() { // 注意:不是const
return n += N; // 因此,n的状态可以发生变化
}
int f() const { // const应用于this,并传递到它的成员
// return g(); // 非法,因为g()并非const
return n + 1;
}
};
int f(const int &n) { // f()不会改变参数n
return X{ n }.f() + 1; // X::X(int) 按值接受参数,因此n保持不变
}
int main() {
int a = 4;
a = f(a); // main()中a不是const对象
}
const限定符标明限定的对象在当前作用域内可视为不可变,例如:将对象标记为const则在其标记的上下文中,该对象不可修改。对于类成员而言,const保证通过const成员函数传递性地维持,即const成员函数不能修改*this的成员,也不能调用同一对象的非const成员函数。前述示例中,X::f是const成员函数,不能调用不提供此保证的X::g;若允许X::f调用X::g,将破坏const的保证,所以X::g可以修改*this,而X::f不能。
const限定符在C++中广为人知且有完善文档记录。保持“const正确性”通常会视为良好的代码规范,也是实践中应当追求的目标;在适用的地方使用const是C++语言最强大的优势,许多标榜“类型安全”的语言都缺乏这一关键特性。若缺少,则正确性将难以实现。
volatile关键字是const的对应物,术语“cv限定符”可同时涵盖这二者。令人遗憾的是,标准中对volatile的定义严重不足,使其具有多重含义。
当应用于基本类型时(例如volatile int),则限定的对象可能通过编译器未知,且以源码不可见的方式访问。因此,这个术语主要用于编写设备驱动程序,其中程序外部操作(如按键的物理压力)可改变与该对象关联的内存,或者当外部硬件/软件组件能观测该对象状态的变化。
非正式地说,如果源码声明“请读取这个volatile对象的值”,生成的代码就应该执行读取操作,即使程序看似没有以任何方式修改它;同样地,如果源码声明“请写入这个volatile对象”,就应该执行内存写入操作,即使程序后续看似不会读取该内存位置。volatile可视为阻止编译器执行优化操作的手段。在C++的抽象中,访问volatile限定的对象在道德上等同于I/O操作 —— 可能改变程序状态。对于类类型的对象,volatile可以像const一样应用于成员函数。实际上,非静态成员函数可以限定为const、volatile、const volatile或都不限定(以及其他可能性)。
前文通过X::f成员函数描述了const限定符应用于成员函数时的含义 —— 此时*this是const的;该函数中,其非mutable的非静态数据成员都是const的,且通过this能调用的唯一非静态成员函数,就是那些const限定的函数。类似地,限定为volatile的非静态成员函数中 —— *this在该函数执行期间为volatile,其所有成员也都是如此,这将影响对这些对象执行的操作。例如,获取volatile int的地址将得到volatile int*,不能隐式转换为int*,这种转换会丢弃某些安全保证,这正是我们需要强制转换的原因(之一)。