常量关键词

前两章分别讲了 const 和 constexpr。C++ 还提供 consteval 与 constinit,历史代码里也会遇到用宏和枚举定义的常量。本章把同族关键词放进一张表,解决“什么时候用哪个”的选型问题。

关键词对照

关键词 可变性 求值时机 链接与共享 典型场景
const 不可通过该名字修改 允许运行时确定 命名空间作用域默认内部链接 运行时配置、读入后不再修改的值
constexpr 同 const,并要求编译期确定 编译期 头文件共享用 inline constexpr 尺寸、阈值等编译期规则
consteval 修饰函数:调用必须发生在编译期 强制编译期 修饰函数而非变量 只在编译期使用的计算
constinit 初始化在编译期完成,对象本身仍可修改 编译期初始化 常与全局变量配合 保证静态变量启动时已初始化
#define 无类型、文本替换 预处理期 无作用域、无类型 历史代码,新代码不用于常量

C++11 之前,枚举值承担过整型常量的角色。如今这个角色首选仍是 constexpr。consteval 与 constinit 各自解决更专门的问题,遇到时按上表定位。头文件里常量与函数对 inline 要求不同的完整对照,见 constexpr 的「常量与函数」。

场景选型

选型只看值从哪里来、放在哪里:

  • 运行时才知道的配置用 const。
  • 编译期规则或尺寸用 constexpr,写在供多个 .cpp 共享的头文件里时加 inline。
  • 只在编译期完成的计算用 consteval。
  • 需要保证静态存储期变量在启动时完成初始化的场景用 constinit。
  • 有类型的常量不用 #define。

常见错误

const 与 constexpr 混同

二者都阻止通过该名字修改,但 constexpr 还要求初始化结果在编译期确定,运行时才知道的配置仍然使用 const。

用 #define 定义有类型常量

#define kMaxCount 5 没有类型和作用域,替换发生在预处理期,报错也难读;写成 constexpr int kMaxCount{5}; 后,类型检查和作用域规则全部生效。

自测问题

  1. 运行时读入的配置值选 const 还是 constexpr?

    查看答案

    选 const。值在编译期未知,不满足 constexpr 的编译期要求,读入后不再修改正是 const 的场景。

  2. 供多个 .cpp 共享的头文件里的棋盘尺寸常量应该怎么写?

    查看答案

    放在具名命名空间里写成 inline constexpr,例如 board::kRows,所有包含该头文件的 .cpp 共享同一个实体。

  3. 为什么新代码不用 #define 定义有类型常量?

    查看答案

    宏没有类型和作用域,只是预处理期的文本替换。constexpr 变量保留类型检查、作用域规则和更好的错误信息。

本章回顾

你已经能按“值从哪里来、放在哪里”做常量关键词选型。用 const-variables.cpp 和 constexpr.cpp 练习判断每个常量的选法,下一章进入类型与边界。