借助 c++11 的新特性,我们可以进行 In-class 成员初始化。但是仍然不能在类中定义静态数据成员。
class A
{
static const int i = 10;
int j = 10;
const int k = 20;
static int m = 10; // error: non-const static data member must be initialized out of line
};
Run Code Online (Sandbox Code Playgroud)
为什么不提供此功能?
首先,这与静态成员初始化完全不同。
类内成员初始化只是转换为构造函数初始化列表的语法糖。
例如
struct X
{
int a_ = 24;
int b_ = 11;
int c_;
X(int c) : c_{c}
{
}
X(int b, int c) : b_{b}, c_{c}
{
}
};
Run Code Online (Sandbox Code Playgroud)
几乎相当于:
struct X
{
int a_;
int b_;
int c_;
X(int c) : a_{24}, b{11}, c_{c}
{
}
X(int b, int c) : a{24}, b_{b}, c_{c}
{
}
};
Run Code Online (Sandbox Code Playgroud)
只是语法糖。在 C++11 之前,没有什么不能用更冗长的代码完成的。
这里的事情更复杂,因为静态数据成员必须只有 1 个符号。您应该阅读ODR(一个定义规则)。
让我们从 const 静态数据成员开始。您可能会惊讶于只允许从编译时常量表达式进行初始化:
auto foo() { return 24; }
constexpr auto bar() { return 24 };
struct X
{
static const int a = foo(); // Error
static const int b = bar(); // Ok
};
Run Code Online (Sandbox Code Playgroud)
实际规则(本身不是规则,但如果您愿意,可以进行推理)更通用(对于常量和非常量静态数据成员):静态数据成员初始化,如果符合要求,必须是编译时表达式。这实际上意味着行初始化中唯一允许的静态数据成员是具有 constexpr 初始化的 const 静态数据成员。
现在让我们看看背后的原因:如果你有一个行内初始化,这将使它成为一个定义,这意味着每个X出现定义的编译单元都将有一个X::a符号。每个这样的编译单元都需要初始化静态成员。在我们的示例中,foo将为直接或间接包含定义为 的标头的每个编译单元调用X。
这样做的第一个问题是出乎意料。调用的次数foo将取决于包含 的编译单元的数量X,即使您foo为单个静态成员的单个初始化编写了单个调用。
但是有一个更严重的问题:foo不是constexpr函数,没有什么可以阻止foo在每次调用时返回不同的结果。所以你最终会得到一堆X::a应该在 ODR 下的符号,但每个符号都用不同的值初始化。
如果您仍然不相信,那么还有第三个问题:有多个定义X::a只会违反 ODR。所以......前两个问题只是 ODR 存在的一些动机。
在单个编译单元中强制X::a定义和X::a: 是允许正确定义和初始化的唯一方法。您仍然可以搞砸并在标题中编写行外定义和初始化,但是对于行内初始化,您肯定有多个初始化。
正如 nm 自 C++17 以来显示的那样,您有inline数据成员,在这里我们允许进行类内初始化:
struct X
{
static inline int i = foo();
};
Run Code Online (Sandbox Code Playgroud)
现在我们可以理解为什么:inline编译器将只选择X::i(来自一个编译单元)的一个定义,因此您只有一个从一个编译单元中选择的初始化表达式的评估。请注意,尊重 ODR 仍然是您的职责。