Note: 在C++中,class类型的静态常量数据成员分为两种:static const
的和static constexpr
的。
Note: 不同编译器对C++标准的支持程度不一样,在某个编译器上执行没问题不代表它一定符合了C++标准。
若整型或枚举类型的静态数据成员被声明为 const
(且非 volatile
),则它能以身为常量表达式的类内初始值,直接在类定义内初始化,这不是必须的,也可以在类外初始化:
struct X
{
const static int n = 1;
static const int m{2}; // C++11 起
const static int k; // 在类外初始化
};
const int X::k = 3;
C++11起,若将字面类型 (LiteralType) 的静态数据成员声明为 constexpr
,则它必须以每个表达式均为常量表达式的类内初始值,直接在类定义内初始化:
struct X {
constexpr static int arr[] = { 1, 2, 3 }; // OK
constexpr static std::complex<double> n = {1,2}; // OK
constexpr static int k; // 错误:constexpr 静态数据成员要求类内初始化
};
ODR式 使用 :
对象被 ODR 式使用: 读取(除非它是编译时常量)或写入对象的值,取对象的地址,或将引用绑定到它;
引用被 ODR 式使用: 引用有被使用,且其所引用者在编译时未知;
函数被 ODR 式使用: 调用函数或取其地址。
C++11前 :
若 const
静态数据成员被 ODR 式使用,则仍需要在 命名空间作用域(即全局作用域)处 定义,但它不能在全局作用域中重复初始化。
struct X {
static const int n = 1; // 声明
};
const int *p = &X::n; // X::n 被 ODR 式使用
const int X::n; // 定义并使用类内初始值初始化
C++11起,C++17前 :
若 const
静态数据成员或 constexpr
静态数据成员被 ODR 式使用,则仍需要在命名空间作用域(即全局作用域)处定义,但它不能重复初始化。
struct X {
static const int n = 1; // 声明
static constexpr int m = 4; // 声明
};
const int *p = &X::n, *q = &X::m; // X::n 与 X::m 被 ODR 式使用
const int X::n; // 定义并使用类内初始值初始化
constexpr int X::m; // 在C++11~C++17中,这是一个定义
C++17起 :
若 const
(且非 inline
)静态数据成员被 ODR 式使用,则仍需要在命名空间作用域(即全局作用域)处定义,但它不能重复初始化。
constexpr
静态数据成员无论有没有被 ODR 式使用,都不需要在全局作用域处再次定义。当然可以在全局作用域处提供“定义”,尽管这是冗余的。
若静态数据成员被声明为 constexpr
,则它隐式 inline
且不需要在命名空间作用域进行重声明。虽然这种无初始化值的重声明(之前则为必要,如上所示)仍然得到容许,但已被弃用。
struct X {
static const int n = 1; // 声明
static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m; // X::n 与 X::m 被 ODR 式使用
const int X::n; // 一直都必须要定义
constexpr int X::m; // C++17 中的 X::m 不需要,可以注释掉
为了解答C++17起,关于`constexpr` 静态数据成员在全局作用域定义,为什么是冗余的?我们通过以下C++代码和汇编代码进行比较。
Note: 下面使用了支持C++17标准的GCC编译器、支持C++11标准的GCC编译器进行对比。因为gcc对C++标准的支持更全面。
例子一:
struct S {
static constexpr int x = 42;
};
constexpr int S::x;
例子二:
struct S {
static constexpr int x = 42;
};
//constexpr int S::x;
例子三:
struct S {
static constexpr int x = 42;
};
//constexpr int S::x;
const int &AA = S::x; // X::m 被 odr 式使用
在例子二中:
编译器看到X::m
没有被 odr-used ,也没有显式定义,就做了一个小小的优化,不给这个常量固定分配空间了。但是它一旦被 odr-used ,编译器仍然会分配空间,就像例子三一样。
分配空间也会标为 weak_definition,像这样的多个定义最终会只取一个。
在例子三中:
可以看出在C++17标准下的constexpr
静态数据成员哪怕没有在全局作用域再次定义,编译器也会给其分配内存空间并初始化。相反的是在C++11标准下的constexpr
静态数据成员如果没有在全局作用域再次定义,编译器就不会给其分配内存空间并初始化,并在它被 odr-used 时报错:undefined reference to S::x
。