C++11 allows in-class initialization of non-static and non-const members. What changed?
在C++ 11之前,我们只能对整型或枚举类型的静态const成员进行类初始化。Stroustrup在他的C++ FAQ中讨论了这一点,给出了下面的例子:
1 2 3 4 5 | class Y { const int c3 = 7; // error: not static static int c4 = 7; // error: not const static const float c5 = 7; // error: not integral }; |
以及以下推理:
So why do these inconvenient restrictions exist? A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.
然而,C++ 11放宽了这些限制,允许非静态成员的类初始化(P.12.62/2):
In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then
- if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;
- otherwise, if the entity is a variant member (9.5), no initialization is performed;
- otherwise, the entity is default-initialized (8.5).
第9.4.2节还允许非常量静态成员的类内初始化,前提是它们用
那么在C++ 03中限制的原因是什么呢?我们只是简单地接受"复杂的链接器规则"还是改变了其他一些使这更容易实现的规则?
简短的回答是,它们保持链接器基本相同,代价是使编译器比以前更加复杂。
也就是说,它仍然只产生一个定义,而不是导致链接器要排序的多个定义,编译器必须将其排序。
它还导致了一些更复杂的规则,程序员也可以保持整理,但它基本上足够简单,这不是一个大问题。如果为单个成员指定了两个不同的初始值设定项,则会出现额外规则:
1 2 3 4 5 6 | class X { int a = 1234; public: X() = default; X(int z) : a(z) {} }; |
现在,这一点上的额外规则处理使用非默认构造函数时用来初始化
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <iostream> class X { int a = 1234; public: X() = default; X(int z) : a(z) {} friend std::ostream &operator<<(std::ostream &os, X const &x) { return os << x.a; } }; int main() { X x; X y{5678}; std::cout << x <<" " << y; return 0; } |
结果:
1 2 | 1234 5678 |
我猜推理可能是在模板最终确定之前写的。毕竟,静态成员的类初始化器所需的"复杂链接规则"对于C/C++ 11来说已经是必须的,以支持模板的静态成员。
考虑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed, // thanks @Kapil for pointing that out // vs. template <class T> struct B { static int s; } template <class T> int B<T>::s = ::ComputeSomething(); // or template <class T> void Foo() { static int s = ::ComputeSomething(); s++; std::cout << s <<" "; } |
编译器的问题在这三种情况下都是一样的:它应该在哪个翻译单元中发出
更直截了当地说:我认为旧标准中给出的推理是完全错误的。
更新
正如Kapil指出的,我的第一个例子在当前的标准(C++ 14)中是不允许的。不管怎样,我还是把它放在了里面,因为IMO是实现(编译器、链接器)最困难的情况。我的观点是:即使是这样的情况也不会比已经允许的更困难,例如在使用模板时。
理论上EDCOX1,6的原因是有效的,但它很容易绕过,这正是C++ 11所做的。
当您包含一个文件时,它只包含该文件并忽略任何初始化。只有在实例化类时,才会初始化成员。
换句话说,初始化仍然与构造函数联系在一起,只是符号不同,而且更方便。如果未调用构造函数,则不会初始化值。
如果调用了构造函数,如果存在,则使用类内初始化初始化初始化值,或者构造函数可以用自己的初始化重写该值。初始化的路径基本上是相同的,即通过构造函数。
这在C++ 11的StruouUp自己的FAQ中是显而易见的。