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

Why can Static data member not be initialized in-class in c++11?

本文关键字:初始化 c++11 静态 数据成员 不能 为什么      更新时间:2023-10-16

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

为什么没有提供此功能?

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

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

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

例如

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之前用更详细的代码做不到的。

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

这里的情况更为复杂,因为静态数据成员必须只有一个符号。您应该阅读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
};

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

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

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

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

如果你仍然不信服,那么就存在3rd问题:对X::a有多个定义只会违反ODR。所以…前两个问题只是ODR存在的一些动机。

强制对X::a进行越界定义是允许在单个编译单元中正确定义和初始化X::a的唯一方法。您仍然可以把行外定义和初始化写在标题中,但对于行内初始化,您肯定有多个初始化。

如n.m.所示,从C++17开始,您就有inline数据成员,这里允许我们进行类初始化:

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

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

它在C++17中提供。

static inline int m = 10; 

Q。为什么C++11中没有提供它?

A。因为当时还没有准备好。(对于每一个新的语言功能,你都可以问同样的问题,答案也总是一样的。)

Q。为什么它需要inline关键字?

A。为了编译器开发的简单性、更好的表达能力和/或与语言的其他部分更好的一致性。很可能是几个因素的加权组合。

"不提供此功能",因为类中非静态成员和静态成员的初始化在语义上非常不同。你之所以惊讶,是因为它们表面上看起来很相似。但事实上,他们没有任何共同点。

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

如果您不关心这些问题,那么从C++17开始,您可以通过声明静态成员inline来明确地告诉编译器您不关心。一旦你做到了,你就可以在类中初始化它了。