C++11 允许非静态和非常量成员的类内初始化.发生了什么变化?
在 C++11 之前,我们只能对整型或枚举类型的静态常量成员执行类内初始化.Stroustrup 在他的 C++ FAQ 中讨论了这个问题,给出了以下例子:
Before C++11, we could only perform in-class initialization on static const members of integral or enumeration type. Stroustrup discusses this in his C++ FAQ, giving the following example:
class Y {
const int c3 = 7; // error: not static
static int c4 = 7; // error: not const
static const float c5 = 7; // error: not integral
};
以及以下推理:
那么为什么会存在这些不方便的限制呢?类通常在头文件中声明,而头文件通常包含在许多翻译单元中.但是,为了避免复杂的链接器规则,C++ 要求每个对象都有唯一的定义.如果 C++ 允许在类中定义需要作为对象存储在内存中的实体,那么这条规则就会被打破.
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 放宽了这些限制,允许在类内初始化非静态成员(第 12.6.2/8 节):
However, C++11 relaxes these restrictions, allowing in-class initialization of non-static members (§12.6.2/8):
在非委托构造函数中,如果给定的非静态数据成员或基类没有由 mem-initializer-id 指定(包括没有 mem-initializer-list 因为构造函数没有ctor-initializer)并且实体不是抽象类(10.4)的虚拟基类,那么
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
- 如果实体是具有大括号或相等初始化器的非静态数据成员,则实体按照 8.5 中的规定进行初始化;
- 否则,如果实体是变体成员(9.5),则不执行初始化;
- 否则,实体默认初始化 (8.5).
- 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 节还允许对非常量静态成员进行类内初始化,前提是它们用 constexpr
说明符标记.
Section 9.4.2 also allows in-class initialization of non-const static members if they are marked with the constexpr
specifier.
那么我们在 C++03 中的限制的原因是什么?我们是只是简单地接受复杂的链接器规则"还是进行了其他更改以使其更易于实现?
So what happened to the reasons for the restrictions we had in C++03? Do we just simply accept the "complicated linker rules" or has something else changed that makes this easier to implement?
推荐答案
简短的回答是他们保持链接器大致相同,代价是编译器仍然比以前更复杂.
The short answer is that they kept the linker about the same, at the expense of making the compiler still more complicated than previously.
即,这不会导致链接器整理出多个定义,它仍然只会产生一个定义,而编译器必须将其整理出来.
I.e., instead of this resulting in multiple definitions for the linker to sort out, it still only results in one definition, and the compiler has to sort it out.
这也导致程序员需要整理一些更复杂的规则,但它大多足够简单,没什么大不了的.当您为单个成员指定了两个不同的初始值设定项时,就会出现额外的规则:
It also leads to somewhat more complex rules for the programmer to keep sorted out as well, but it's mostly simple enough that it's not a big deal. The extra rules come in when you have two different initializers specified for a single member:
class X {
int a = 1234;
public:
X() = default;
X(int z) : a(z) {}
};
现在,当您使用非默认构造函数时,此时的额外规则处理用于初始化 a
的值.答案相当简单:如果您使用未指定任何其他值的构造函数,则 1234
将用于初始化 a
-- 但如果您使用指定其他值的构造函数,则 1234
基本上被忽略.
Now, the extra rules at this point deal with what value is used to initialize a
when you use the non-default constructor. The answer to that is fairly simple: if you use a constructor that doesn't specify any other value, then the 1234
would be used to initialize a
-- but if you use a constructor that specifies some other value, then the 1234
is basically ignored.
例如:
#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;
}
结果:
1234
5678
相关文章