C ++ 11之前,我们只能执行对整型或枚举类型的静态常量成员在类的初始化。 斯特劳斯讨论了这个在他的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
};
而下面的推理:
那么,为什么这些不方便的限制存在? 一类是在头文件通常声明和头文件通常包括成许多翻译单元。 然而,为了避免复杂的连接器规则,C ++要求每个对象都有一个独特的定义。 如果C ++允许类是需要被存储在内存中为对象实体的定义,规则将被打破。
然而,C ++ 11放松这些限制,允许在级非静态成员(§12.6.2/ 8)的初始化:
在一个非委托构造,如果一个给定的非静态数据成员或基类不是由MEM-初始化-ID指定(包括的情况下不存在MEM-初始化列表因为构造不具有构造函数,初始值设定 )与实体不是虚拟基类的抽象类(10.4),那么
- 如果实体是具有大括号或相等的初始值设定的非静态数据成员,如在8.5中指定的实体被初始化;
- 否则,如果该实体是一个变体部件(9.5),不进行初始化;
- 否则,该实体是缺省初始化(8.5)。
第9.4.2节还允许类非const静态成员的初始化,如果他们都标有constexpr
符。
所以,发生了什么原因的限制我们在C ++ 03? 难道我们只是简单地接受了“复杂的连接规则”或有别的事情发生变化,使这个更容易实现?
简短的回答是,他们保持了连接器大致相同,在使编译器仍然比以前更复杂的代价。
即,不是这导致多个定义链接器进行梳理,它仍然只在一个清晰的结果,编译器必须整理出来。
这也导致有些更复杂的规则为程序员保持整理出来为好,但它主要是很简单的,它不是一个大问题。 额外的规则来的,当你有一个成员指定两个不同的初始化:
class X {
int a = 1234;
public:
X() = default;
X(int z) : a(z) {}
};
现在,在这一点上处理的是使用什么值的特殊规则来初始化a
当您使用非默认的构造函数。 这个问题的答案很简单:如果你使用不指定任何其他值构造函数,那么1234
将被用来初始化a
-但如果你使用一个构造函数指定其他值,则1234
基本上是忽略。
例如:
#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;
}
结果:
1234
5678
我猜想,推理可能的模板被完成之前已被写入。 所有的“复杂的连接规则(S)”必需的类静态成员的初始化后/已经是必要的C ++ 11支持的模板静态成员。
考虑
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";
}
编译器的问题是在这三种情况下相同的:在翻译单位应该发出的定义s
必要的初始化和代码? 简单的解决办法是到处散发,并让连接器把它清除出来。 这就是为什么,接头已经支持之类的东西__declspec(selectany)
它只是不会有可能实现C ++ 03离不开它。 这就是为什么它是没有必要延长连接。
为了说得更不客气地说:我想在旧标准中给出的理由是完全错误的。
UPDATE
正如卡皮尔指出的,我的第一示例甚至不允许在当前标准(C ++ 14)。 我离开它,无论如何,因为IMO是实现(编译器,链接器)最困难的情况下。 我的观点是:即使这种情况下,是不是比使用模板时发生了什么已经允许例如,任何困难。
从理论上讲So why do these inconvenient restrictions exist?...
的原因是有效的,但它可以相当容易地绕过,这是C ++ 11做什么。
当你有一个文件,它只是包括文件和忽略任何初始化。 当你实例化类的成员仅初始化。
换句话说,初始化仍与构造打成平手,只是表示法是不同的,更方便。 如果构造不叫,值不被初始化。
如果构造函数被调用时,值与一流初始化初始化如果存在或构造可以用自己的初始化重写。 初始化的路径基本上是相同的,那就是,通过构造。
这从斯特劳斯自己是显而易见的常见问题在C ++ 11。
文章来源: C++11 allows in-class initialization of non-static and non-const members. What changed?