C++11 允许非静态和非常量成员的类内初始化。发生了什么变化?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13662441/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++11 allows in-class initialization of non-static and non-const members. What changed?
提问by Joseph Mansfield
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:
在 C++11 之前,我们只能对整型或枚举类型的静态常量成员执行类内初始化。Stroustrup 在他的 C++ FAQ 中讨论了这个问题,给出了以下例子:
class Y {
const int c3 = 7; // error: not static
static int c4 = 7; // error: not const
static const float c5 = 7; // error: not integral
};
And the following reasoning:
以及以下推理:
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++ 要求每个对象都有唯一的定义。如果 C++ 允许需要作为对象存储在内存中的实体的类内定义,那么这条规则就会被打破。
However, C++11 relaxes these restrictions, allowing in-class initialization of non-static members (§12.6.2/8):
但是,C++11 放宽了这些限制,允许在类中初始化非静态成员(第 12.6.2/8 节):
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-listbecause 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).
在非委托构造函数中,如果给定的非静态数据成员或基类未由mem-initializer-id指定(包括没有mem-initializer-list 的情况,因为构造函数没有ctor-initializer)并且实体不是抽象类(10.4)的虚拟基类,那么
- 如果实体是一个非静态数据成员,它有一个大括号或等号初始化器,实体按照 8.5 中的规定进行初始化;
- 否则,如果实体是变体成员(9.5),则不执行初始化;
- 否则,实体是默认初始化的 (8.5)。
Section 9.4.2 also allows in-class initialization of non-const static members if they are marked with the constexpr
specifier.
第 9.4.2 节还允许非常量静态成员的类内初始化(如果它们用说明constexpr
符标记)。
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?
那么我们在 C++03 中的限制的原因发生了什么?我们是只是简单地接受“复杂的链接器规则”还是进行了其他更改以使其更易于实现?
回答by Jerry Coffin
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 programmerto 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) {}
};
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.
现在,此时的额外规则处理a
使用非默认构造函数时用于初始化的值。答案相当简单:如果您使用未指定任何其他值的构造函数,则1234
将用于初始化a
——但如果您使用指定其他值的构造函数,1234
则 基本上会被忽略。
For example:
例如:
#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 << "\n" << y;
return 0;
}
Result:
结果:
1234
5678
回答by Paul Groke
I guess that reasoning might have been written before templates were finalized. After all the "complicated linker rule(s)" necessary for in-class initializers of static members was/were already necessary for C++11 to support static members of templates.
我想推理可能在模板最终确定之前就已经写好了。毕竟静态成员的类内初始值设定项所需的所有“复杂的链接器规则”对于 C++11 支持模板的静态成员来说是/已经是必要的。
Consider
考虑
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 << "\n";
}
The problem for the compiler is the same in all three cases: in which translation-unit should it emit the definition of s
and the code necessary to initialize it? The simple solution is to emit it everywhere and let the linker sort it out. That's why the linkers already supported things like __declspec(selectany)
. It just wouldn't have been possible to implement C++03 without it. And that's why it wasn't necessary to extend the linker.
编译器的问题在所有三种情况下都是相同的:它应该在哪个翻译单元中发出 的定义s
以及初始化它所需的代码?简单的解决方案是将它发送到任何地方,让链接器将其整理出来。这就是为什么链接器已经支持诸如__declspec(selectany)
. 没有它就不可能实现 C++03。这就是为什么没有必要扩展链接器的原因。
To put it more bluntly: I think the reasoning given in the old standard is just plain wrong.
更直白地说:我认为旧标准中给出的推理完全错误。
UPDATE
更新
As Kapil pointed out, my first example isn't even allowed in the current standard (C++14). I left it in anyway, because it IMO is the hardest case for the implementation (compiler, linker). My point is: even thatcase is not any harder than what's already allowed e.g. when using templates.
正如 Kapil 所指出的,我的第一个示例在当前标准 (C++14) 中甚至不被允许。无论如何我都保留了它,因为 IMO 是实现(编译器、链接器)最困难的情况。我的观点是:即使这种情况也不比已经允许的情况更难,例如在使用模板时。
回答by zar
In theory So why do these inconvenient restrictions exist?...
reason is valid but it can rather be easily bypassed and this is exactly what C++ 11 does.
理论上,So why do these inconvenient restrictions exist?...
原因是有效的,但它可以很容易地绕过,而这正是 C++ 11 所做的。
When you includea file, it simply includes the file and disregards any initialization. The members are initialized only when you instantiatethe class.
当您包含一个文件时,它只是包含该文件并忽略任何初始化。只有在实例化类时才初始化成员。
In other words, the initialization is still tied with constructor, just the notation is different and is more convenient. If the constructor is not called, the values are not initialized.
也就是说,初始化还是和构造函数绑定,只是记法不同,更方便。如果未调用构造函数,则不会初始化这些值。
If the constructor is called, the values are initialized with in-class initialization if present or the constructor can override that with own initialization. The path of initialization is essentially the same, that is, via the constructor.
如果调用构造函数,则使用类内初始化(如果存在)来初始化这些值,或者构造函数可以使用自己的初始化覆盖它。初始化的路径本质上是一样的,就是通过构造函数。
This is evident from Stroustrup own FAQon C++ 11.
从 Stroustrup 自己的关于 C++ 11的常见问题解答中可以明显看出这一点。