借助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
};
为什么不提供此功能?
答案 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
来明确告知编译器您无关紧要。完成此操作后,您将可以在课堂上对其进行初始化。