为什么不能在 c++11 中初始化静态数据成员?

Swa*_*nil 0 c++ static c++11

借助 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)

为什么不提供此功能?

bol*_*lov 6

非静态数据成员类内初始化

首先,这与静态成员初始化完全不同。

类内成员初始化只是转换为构造函数初始化列表的语法糖。

例如

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 仍然是您的职责。