3 回答
![?](http://img1.sycdn.imooc.com/54586431000103bb02200220-100-100.jpg)
TA贡献1830条经验 获得超3个赞
简短的答案是,它们使链接程序保持相同,但代价是使编译器比以前更加复杂。
即,除了导致链接器要定义多个定义之外,它仍然仅生成一个定义,而编译器必须对其进行分类。
这也导致程序员要整理出一些更复杂的规则,但是它非常简单,所以没什么大不了的。当您为单个成员指定了两个不同的初始化程序时,就会出现额外的规则:
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
![?](http://img1.sycdn.imooc.com/545862e700016daa02200220-100-100.jpg)
TA贡献1712条经验 获得超3个赞
我猜想推理可能是在模板完成之前编写的。对于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。这就是为什么没有必要扩展链接器的原因。
坦率地说:我认为旧标准中的推理完全是错误的。
更新
正如Kapil指出的那样,当前标准(C ++ 14)甚至不允许我的第一个示例。无论如何,我还是保留了它,因为IMO是实现过程中最困难的情况(编译器,链接器)。我的观点是:即使是这种情况,也没有比例如使用模板时所允许的情况难。
![?](http://img1.sycdn.imooc.com/5333a1a90001c8d802000200-100-100.jpg)
TA贡献1785条经验 获得超8个赞
从理论上讲,So why do these inconvenient restrictions exist?...原因是有效的,但可以轻松地绕开它,而这正是C ++ 11所做的。
当你有一个文件,它只是包括文件和忽略任何初始化。仅在实例化类时才初始化成员。
换句话说,初始化仍然与构造函数联系在一起,只是表示法有所不同并且更加方便。如果未调用构造函数,则不会初始化值。
如果调用了构造函数,则使用类内初始化(如果存在)初始化值,否则构造函数可以使用自己的初始化覆盖它们。初始化的路径本质上是相同的,即通过构造函数。
从Stroustrup自己在C ++ 11上的常见问题中可以明显看出这一点。
- 3 回答
- 0 关注
- 380 浏览
添加回答
举报