C++ / a working model

04 / 80   ·   C++11   ·   约 8 分钟

const:只读访问路径与常量对象

先记住这句话

const 限制经由某个类型进行的修改,但只读引用不意味着底层对象永不变化。理解顶层与底层 const、成员函数的只读承诺,以及 const 与 constexpr 的区别,才能写出可靠而不过度承诺的接口。

本篇内容
  1. 先问谁不能修改
  2. 接口中的 const 承诺
  3. const 不等于编译期计算
  4. 运行示例
  5. 动手练习

先问谁不能修改

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*,或为该类型另建别名。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录