我有this code:
#include <iostream>
using namespace std;
struct X {
int a = 1;
};
struct Y {
X &_x;
Y(X &x) : _x(x) {}
};
// intentionally typoed version of Y, without the reference in the constructor
struct Z {
X &_x;
Z(X x) : _x(x) {}
};
int main() {
X x;
Y y(x);
Z z(x);
cout << "x: " << &x << endl;
cout << "y.x: " << &y._x << endl;
cout << "z.x: " << &z._x << endl;
}
我一直发现自己忘记了这种格式的类的构造函数中的&
。
这输出以下内容:
x: 0xbfa195f8
y.x: 0xbfa195f8
z.x: 0xbfa195fc
为什么y
和z
的行为会有所不同?
为什么在X &_x
的构造函数中使用X
类型的实例初始化Y
成员并不是错误?
答案 0 :(得分:4)
您的代码接近于具有未定义的行为:您绑定对x
的构造函数返回时超出范围的对象(Z
)的引用,这使其成为悬空引用。您不会尝试在以后取消引用它,这就是UB不显示的原因。但是试着读出z.x
的价值,而不是取其地址,例如UB。
分配引用不是错误的原因是允许左值引用绑定到左值,x
是左值。在 _x
的构造函数返回后,编译器无需确定是否要访问Z
甚至(事实上,我担心这在一般情况下是不可能的情况)。
但是,当您尝试将引用绑定到本地对象时,一个不错的编译器至少应该发出警告:尝试使用/Wall
选项进行编译,您应该得到一个。
关于y.x
和z.x
之间输出的差异:嗯,您正在打印这些变量的地址,而不是它们的值,而引用只是它们引用的变量的别名。因此,获取引用的地址会产生与获取它所绑定的变量的地址相同的结果。
对于z
,引用z.x
未绑定到您在x
中声明的变量main()
(与y.x
不同),但是(实际上)绑定到一个超出范围的对象并保持Z
的构造函数的参数(通过值传递创建参数的副本,并{{ 1}}的引用是该副本的别名)。因此,z
运算符返回不同的地址。
答案 1 :(得分:3)
为什么y和z的情况下行为不同?
由于Y
通过引用获取参数,Z
不会。 Y
对原始对象进行操作,Z
在副本上运行。
为什么在Y的构造函数中使用类型X的实例初始化X&amp; _x成员并不是错误?
诊断不是强制性的。超出构造函数的范围,引用Z::_x
无效。因此访问它是无效的(但是,在构造函数中访问_x
是可以的)。
答案 2 :(得分:2)
在调用构造函数时会创建对象的副本,因此z.x的输出不同。该对象的生命周期非常有限 - 它仅存在于构造函数中。这是未定义的行为,引用将无效。
为防止应用程序中出现此类行为,最好将复制构造函数和赋值运算符标记为私有。