为什么在C ++ 11中未在类中初始化静态数据成员?

时间:2018-08-02 17:20:18

标签: c++ c++11 static

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

为什么不提供此功能?

3 个答案:

答案 0 :(得分:4)

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

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

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

例如

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 (One Definition Rule)的信息。

让我们从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和非const静态数据成员):静态数据成员初始化(如果符合)必须为编译时间表达。这实际上意味着,行初始化中唯一允许的静态数据成员是使用constexpr初始化的const静态数据成员。

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

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

但是,还有一个更严重的问题:foo不是constexpr函数,没有什么可以阻止foo在每次调用时返回不同的结果。因此,您将得到一堆X::a符号,这些符号应在ODR下,但每个符号都使用不同的值初始化。

如果您仍然不确定,那么就会遇到3 rd 问题:具有X::a的多个定义将完全违反ODR。所以...前两个问题仅仅是ODR存在的一些动机。

强制对X::a进行界外定义是在单个编译单元中允许正确定义和初始化X::a的唯一方法:您仍然可以弄乱并在标头中写入离线定义和初始化,但是使用在线初始化,您肯定会有多个初始化。

现在从C ++ 17开始显示,您拥有inline数据成员,在这里我们可以进行类内初始化:

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

现在我们可以理解为什么:借助inline,编译器将只选择X::i的一个定义(来自一个编译单元),因此您只有一个对从一个表达式中选择的初始化表达式的求值。编译单元。请注意,尊重ODR仍然是您的责任。

答案 1 :(得分:1)

它在C ++ 17中提供。

 static inline int m = 10; 
问:为什么C ++ 11中没有提供它?

A。因为那时还没有准备好。 (您可以对每个新的语言功能提出相同的问题,答案也总是相同的。)

问:为什么需要内联关键字?

A。为了简化编译器开发,提高表达性和/或与语言的其他部分实现更好的一致性。很可能是几种因素的权衡组合。

答案 2 :(得分:1)

“未提供此功能”,因为非静态和静态成员的类内初始化在语义上是非常不同的功能。您的惊讶是基于它们表面上看起来相似的事实。但实际上,它们实际上没有任何共同之处。

静态数据成员的类内声明就是这样-一个声明。它不为该成员提供定义定义必须单独提供。 定义在程序代码中的位置将产生后果。例如。它还将为该静态数据成员定义初始化行为(初始化顺序),并且将影响目标文件中导出的符号。这就是为什么要为该定义选择位置是您的责任。该语言希望您这样做,并且希望您明确地执行它。这些问题不适用于非静态成员,这使它们在本质上有所不同。

如果您不关心此类问题,可以从C ++ 17开始,通过声明您的静态成员inline来明确告知编译器您无关紧要。完成此操作后,您将可以在课堂上对其进行初始化。