04 / 80 · C++11 · 约 8 分钟
const:只读访问路径与常量对象
const 限制经由某个类型进行的修改,但只读引用不意味着底层对象永不变化。理解顶层与底层 const、成员函数的只读承诺,以及 const 与 constexpr 的区别,才能写出可靠而不过度承诺的接口。
先问谁不能修改
const int* p 是指向只读 int 的指针:可以改变 p 指向哪里,不能经由 p 写入整数。int* const p 则固定指针本身,但允许修改所指的非 const 整数。const int* const p 同时施加两种限制,阅读声明时必须区分这两个层次。
只读访问路径不保证对象在别处不变。一个普通 int 可以同时被 int& 和 const int& 引用,前者修改后,后者能观察到新值。相反,若对象定义时就是 const,绕过类型限制实际修改它会产生未定义行为,而非可靠的“强制修改”。
接口中的 const 承诺
对较大的只读输入,const T& 避免复制并约束函数通过该引用修改对象;小整数通常直接按值传递即可。值参数的顶层 const 不参与区分函数类型,所以不能用 f(int) 和 f(const int) 创建两个重载。
const 成员函数限制通过 this 修改普通成员,并使函数可用于 const 对象。它不是深层不可变保证:指针成员所指对象可能仍可修改,mutable 成员也能改变。接口应表达逻辑上的只读,而不是把 const 当作自动线程安全标记。
const 不等于编译期计算
const 变量可以在运行时初始化,初始化之后不能经由正常访问修改;constexpr 变量则要求满足常量表达式初始化等规则,并隐含 const。某些以常量表达式初始化的 const 整数也能用于编译期场景,但不能据此把所有 const 对象都视为编译期常量。
示例让只读引用观察普通对象的合法变化,随后展示 const 指针可以修改其目标。实际开发应尽量从接口起点保留 const,避免在深层代码中用 const_cast 补救错误签名;这样的转换会使真正的修改权限更难审查。
容易答错的地方
- const 指针成员仅固定指针值,不会递归地冻结指针指向的对象。
- auto x = const_object 通常推导出非 const 的值副本;要保持引用和只读性,明确使用 const auto&。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <iostream>
int main() {
int value = 3;
const int& read_only = value;
value = 7;
assert(read_only == 7);
int* const fixed_pointer = &value;
*fixed_pointer = 9;
const int* read_pointer = &value;
assert(*read_pointer == 9);
constexpr int limit = 10;
static_assert(limit > 0, "positive limit");
std::cout << read_only << ' ' << limit << '\n';
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-const.cpp -o example && ./example预期结果
9 10
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
using P = int*; const P p = &n; 中,p 是指向 const int 的指针吗?能否写 *p = 5?
查看参考答案
不是。P 已经代表完整指针类型,const P 等价于 int* const,const 修饰指针本身。只要 n 是存活的非 const int,*p = 5 合法;p 不能改指向其他对象。若要指向只读 int,应写 const int*,或为该类型另建别名。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。