首页 > 解决方案 > 为什么不能在 C++11 中初始化静态数据成员?

问题描述

借助 c++11 的新特性,我们可以进行类内成员初始化。但是仍然不能在类中定义静态数据成员。

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
};

为什么不提供此功能?

标签: c++c++11static

解决方案


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

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

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

例如

struct X
{
   int a_ = 24;
   int b_ = 11;
   int c_;

   X(int c) : c_{c}
   {
   }

   X(int b, int c) : b_{b}, c_{c}
   {
   }
};

几乎相当于:

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}
   {
   }
};

只是语法糖。在 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
};

实际规则(本身不是规则,但如果你愿意,可以推理)更一般(对于 const 和非常量静态数据成员):静态数据成员初始化,如果符合,必须是编译时表达式。这实际上意味着行初始化中唯一允许的静态数据成员是具有 constexpr 初始化的 const 静态数据成员。

现在让我们看看这背后的原因:如果你有一个行内初始化,这将使它成为一个定义,这意味着X出现定义的每个编译单元都会有一个X::a符号。每个这样的编译单元都需要初始化静态成员。在我们的示例foo中,将为每个直接或间接包含定义为X.

第一个问题是它出乎意料。调用的次数foo将取决于包含的编译单元的数量X,即使您foo为单个静态成员的单个初始化编写了单个调用。

但是还有一个更严重的问题:foo不是一个constexpr函数,没有什么能阻止foo在每次调用时返回不同的结果。所以你最终会得到一堆X::a符号,它们应该在 ODR 下,但每个符号都用不同的值初始化。

如果您仍然不相信,那么还有第三个问题:拥有多个定义X::a只会违反 ODR。所以……前两个问题只是 ODR 存在的一些动机。

强制定义 forX::a是允许X::a在单个编译单元中正确定义和初始化 : 的唯一方法。您仍然可以在标头中搞砸并编写行外定义和初始化,但是通过行内初始化,您肯定有多个初始化。

正如 nm 自 C++17 以来所显示的,您有inline数据成员,在这里我们可以进行类内初始化:

struct X
{
    static inline int i = foo();
};

现在我们可以理解为什么了:inline编译器只会选择X::i(来自一个编译单元)的一种定义,因此您只需对从一个编译单元中选择的初始化表达式进行一次评估。请注意,尊重 ODR 仍然是您的责任。


推荐阅读