类内定义编译期常量应使用static constexpr,它类型安全、可作模板参数且不占对象内存;const非静态成员不能在类内直接初始化,static const仅为链接期常量;const/引用成员必须在初始化列表中按声明顺序初始化;constexpr构造函数配合初始化列表才能使类成为字面量类型;初始化列表中表达式求值顺序未指定,需警惕生命周期与重复计算问题。

类内定义常量用 static constexpr,不是 const
非静态 const 成员不能在类定义里直接赋值(C++11 之前甚至不允许初始化),而 static constexpr 才是现代 C++ 中定义类内编译期常量的正解。它既保证类型安全、可做模板参数,又不占用每个对象的内存。
-
static const int MAX_SIZE = 100;可以,但只是“链接期常量”,无法用于需要编译期常量的场景(比如数组大小、模板非类型参数) -
static constexpr double PI = 3.14159;推荐,类型明确、编译期求值、可推导类型(auto也行) - 别写
const int x = 42;—— 这会报错:非静态成员不能带默认初始化器(除非是 in-class initializer + C++11 起支持,但它仍是运行时构造,不是常量表达式)
成员初始化列表必须覆盖所有 const 和引用成员
一旦类里有 const 成员或引用成员,它们只能在初始化列表里“出生时”赋值,构造函数体内赋值是非法的(编译错误)。漏掉任何一个,编译器立刻报错,且错误信息往往指向构造函数签名,而不是具体哪个成员没初始化。
- 错误现象:
error: uninitialized const member或reference member is not initialized - 正确写法:
MyClass(int v) : val(v), ref(other_var) {},其中val是const int,ref是int& - 注意顺序:初始化顺序严格按成员在类中声明的顺序,不是按初始化列表里的书写顺序 —— 写反了容易引发未定义行为(比如用未初始化的成员去初始化另一个成员)
constexpr 构造函数 + 初始化列表才能让对象成为字面量类型
想让类对象能放进 constexpr 上下文(比如作为模板参数、存进 std::array 静态存储区),光有 static constexpr 成员不够,整个类还得是字面量类型(literal type),这要求构造函数本身是 constexpr,且所有成员都能通过初始化列表完成常量初始化。
- 必须满足:
constexpr MyClass(int x) : data(x) {},且data是const int或其他字面量类型成员 - 不能在构造函数体里做任何运行时操作(比如
new、std::cout、调用非constexpr函数) - 如果成员是自定义类型,它自己也得有
constexpr构造函数,否则链式失败
初始化列表里调用函数要小心生命周期和求值顺序
初始化列表中的表达式会在进入构造函数体前执行,但它们的求值顺序是未指定的(C++17 起仅对逗号运算符保证左到右,但初始化列表不是逗号表达式)。如果多个初始化子句依赖同一个临时对象或局部变量,很容易踩坑。
立即学习“C++免费学习笔记(深入)”;
- 危险写法:
A(int x) : a(func()), b(func()) {}——func()调用几次?顺序?不确定 - 更危险:
A() : ref(*ptr), ptr(new int(42)) {}——ptr还没初始化,*ptr就被用了,UB - 稳妥做法:把复杂逻辑提到构造函数体里,或拆成辅助
static constexpr函数;若必须复用,先算好再传入列表:auto tmp = expensive_calc(); A(tmp) : a(tmp), b(tmp * 2) {}
构造函数初始化列表看着简单,但涉及常量性、生命周期、求值顺序三重约束,稍微松懈就从编译错误变成运行时 UB。尤其当类开始嵌套、模板化之后,漏掉一个 constexpr 或错位一个初始化顺序,调试成本远高于写的时候多花十秒检查。










