将另一个实例添加到同一向量后,对成员变量的引用中断
Reference to member variable broken after adding another instance to the same vector
考虑以下代码:
struct Point {
int x;
int& xref = x;
};
int main() {
std::vector<Point> points;
for (int i = 0; i < 2; i++) {
Point p;
p.x = i;
points.push_back(p);
assert (points[0].x == points[0].xref);
}
return 0;
}
第二次迭代断言失败。为什么?
我正在使用 GNU C++ 编译器。我测试过的所有标准都会出现问题:
- c++11
- c++14
- c++17
- gnu++11
- gnu++14
- gnu++17
每个元素的 xref
似乎引用了最后添加的元素的 x
而不是它自己的,如下所示:https://pastebin.com/H4wCszxp
当你把一个对象推入一个向量时,你就做了一个副本†。
当您复制引用(即成员)时,新引用将引用与复制来源相同的对象。因此,如果您将 Point p
复制到 Point copy
,那么 copy::xref
将引用 p.x
,因为那是 p.xref
所引用的内容。
因此,向量中的所有 Point
对象都是在循环范围内构造的 Point p
自动变量的副本。因此,向量中的所有这些对象都引用作为自动变量 p
成员的 int
对象。 None 其中指的是他们自己的 x
成员。
在第一次迭代期间这很好,因为 points[0].xref
指的是现有的 p.x
,它也具有与 points[0].x
相同的值。但在该迭代结束时,自动变量 p
(points[0].xref
引用其成员)被销毁。此时 points[0].xref
是一个不再引用有效对象的悬挂引用。在下一次迭代中使用参考。使用悬空引用具有未定义的行为。
如果你想访问this对象,那么使用this
指针。如果要存储对对象的引用,则不要期望该引用的副本引用另一个对象。避免将引用(或指针)存储到生命周期短于持有引用的对象的对象。
† ... 或在压入右值时移动。您不推送右值,并且副本与 Point
) 的移动完全相同,因此这是无关紧要的细节。
发生这种情况是因为当将点插入向量时它会被复制,并且引用指向原始值。
例如。这项工作很好:
int main() {
Point p;
p.x = 100;
assert (p.x == p.xref);
return 0;
}
我怀疑您可能对声明 int& xref = x;
的工作方式有一点误解。这只是一个默认的初始化。
- 它不保证
xref
它总是被构造为引用 x
。默认复制构造函数将复制源实例的外部参照。
- 此外,您可以使用初始化列表更改外部参照绑定。
我将首先使用您所看到的简化版本进行说明,然后使用您的代码的修改版本。最后,我将对您的 Point
对象进行调整,使其按照您的预期运行。
int main()
{
Point p;
p.x = 1;
std::cout << &p.x << "(" << p.x << ") " << &p.xref << "(" << p.xref << ")\n";
Point p2 = p;
std::cout << &p2.x << "(" << p2.x << ") " << &p2.xref << "(" << p2.xref << ")\n";
p2.x = 2;
std::cout << &p2.x << "(" << p2.x << ") " << &p2.xref << "(" << p2.xref << ")\n";
Point p3 {3, p2.x};
std::cout << &p3.x << "(" << p3.x << ") " << &p3.xref << "(" << p3.xref << ")\n";
return 0;
}
示例输出:
//The in value of p.x and p.xref are in the same location
0x7ffde0450670(1) 0x7ffde0450670(1)
//p2 is initialised as a copy of p
//So p2.x has its own location but value is copied
//and p2.xref refers to the same location as p.xref (and hence p.x)
0x7ffde0450680(1) 0x7ffde0450670(1)
//Therefore updating p2.x doesn't affect p2.xref
0x7ffde0450680(2) 0x7ffde0450670(1)
//All members of p3 are initialised with initialiser list
//p3.x has its own location
//p3.xref is assigned location of p2.x
0x7ffde0450690(3) 0x7ffde0450680(2)
图形上看起来像这样:
p-----> +-------------------------+
+->| x = 1 |
| | xref = 0x7ffde0450670 --|--+
| +-------------------------+ |
|-------------------------------+
|
+--------------------------------+
p2-----> +-------------------------+ |
+-> | x = 2 | |
| | xref = 0x7ffde0450670 --|--+
| +-------------------------+
+--------------------------------+
p3-----> +-------------------------+ |
| x = 3 | |
| xref = 0x7ffde0450680 --|--+
+-------------------------+
稍微修改您的代码:
std::vector<Point> points;
for (int i = 0; i < 2; i++) {
Point p;
p.x = i;
points.push_back(p);
std::cout << i << " " << &p << "\n";
std::cout << &p.x << "(" << p.x << ") " << &p.xref << "(" << p.xref << ")\n";
std::cout << &points[0].x << "(" << points[0].x << ") " << &points[0].xref << "(" << points[0].xref << ")\n";
}
示例输出:
0 0x7fffa80e0840
0x7fffa80e0840(0) 0x7fffa80e0840(0)
0x55bd4ff52c30(0) 0x7fffa80e0840(0)
//On each iteration p is in the same location
1 0x7fffa80e0840
//So p.x is in the same location
0x7fffa80e0840(1) 0x7fffa80e0840(1)
//And the vector elements are initialised with p
//So each element's xref is the same as p.x and p.xref
0x55bd4ff52c50(0) 0x7fffa80e0840(1)
最后,通过对 Point
构造函数进行一些控制,您可以使其按预期运行。
struct Point {
int x;
int& xref = x;
Point() = default;
Point(const Point& p): x(p.x) {}
};
以上代码执行以下操作:
- 它将默认的复制构造函数替换为仅从源复制
x
值的复制构造函数。因此 xref
保留其默认初始化。
- 由于定义了您自己的构造函数,默认构造函数被删除。所以现在你需要显式地声明它;幸运的是,您可以指定默认实现就足够了。
考虑以下代码:
struct Point {
int x;
int& xref = x;
};
int main() {
std::vector<Point> points;
for (int i = 0; i < 2; i++) {
Point p;
p.x = i;
points.push_back(p);
assert (points[0].x == points[0].xref);
}
return 0;
}
第二次迭代断言失败。为什么?
我正在使用 GNU C++ 编译器。我测试过的所有标准都会出现问题:
- c++11
- c++14
- c++17
- gnu++11
- gnu++14
- gnu++17
每个元素的 xref
似乎引用了最后添加的元素的 x
而不是它自己的,如下所示:https://pastebin.com/H4wCszxp
当你把一个对象推入一个向量时,你就做了一个副本†。
当您复制引用(即成员)时,新引用将引用与复制来源相同的对象。因此,如果您将 Point p
复制到 Point copy
,那么 copy::xref
将引用 p.x
,因为那是 p.xref
所引用的内容。
因此,向量中的所有 Point
对象都是在循环范围内构造的 Point p
自动变量的副本。因此,向量中的所有这些对象都引用作为自动变量 p
成员的 int
对象。 None 其中指的是他们自己的 x
成员。
在第一次迭代期间这很好,因为 points[0].xref
指的是现有的 p.x
,它也具有与 points[0].x
相同的值。但在该迭代结束时,自动变量 p
(points[0].xref
引用其成员)被销毁。此时 points[0].xref
是一个不再引用有效对象的悬挂引用。在下一次迭代中使用参考。使用悬空引用具有未定义的行为。
如果你想访问this对象,那么使用this
指针。如果要存储对对象的引用,则不要期望该引用的副本引用另一个对象。避免将引用(或指针)存储到生命周期短于持有引用的对象的对象。
† ... 或在压入右值时移动。您不推送右值,并且副本与 Point
) 的移动完全相同,因此这是无关紧要的细节。
发生这种情况是因为当将点插入向量时它会被复制,并且引用指向原始值。 例如。这项工作很好:
int main() {
Point p;
p.x = 100;
assert (p.x == p.xref);
return 0;
}
我怀疑您可能对声明 int& xref = x;
的工作方式有一点误解。这只是一个默认的初始化。
- 它不保证
xref
它总是被构造为引用x
。默认复制构造函数将复制源实例的外部参照。 - 此外,您可以使用初始化列表更改外部参照绑定。
我将首先使用您所看到的简化版本进行说明,然后使用您的代码的修改版本。最后,我将对您的 Point
对象进行调整,使其按照您的预期运行。
int main()
{
Point p;
p.x = 1;
std::cout << &p.x << "(" << p.x << ") " << &p.xref << "(" << p.xref << ")\n";
Point p2 = p;
std::cout << &p2.x << "(" << p2.x << ") " << &p2.xref << "(" << p2.xref << ")\n";
p2.x = 2;
std::cout << &p2.x << "(" << p2.x << ") " << &p2.xref << "(" << p2.xref << ")\n";
Point p3 {3, p2.x};
std::cout << &p3.x << "(" << p3.x << ") " << &p3.xref << "(" << p3.xref << ")\n";
return 0;
}
示例输出:
//The in value of p.x and p.xref are in the same location 0x7ffde0450670(1) 0x7ffde0450670(1) //p2 is initialised as a copy of p //So p2.x has its own location but value is copied //and p2.xref refers to the same location as p.xref (and hence p.x) 0x7ffde0450680(1) 0x7ffde0450670(1) //Therefore updating p2.x doesn't affect p2.xref 0x7ffde0450680(2) 0x7ffde0450670(1) //All members of p3 are initialised with initialiser list //p3.x has its own location //p3.xref is assigned location of p2.x 0x7ffde0450690(3) 0x7ffde0450680(2)
图形上看起来像这样:
p-----> +-------------------------+ +->| x = 1 | | | xref = 0x7ffde0450670 --|--+ | +-------------------------+ | |-------------------------------+ | +--------------------------------+ p2-----> +-------------------------+ | +-> | x = 2 | | | | xref = 0x7ffde0450670 --|--+ | +-------------------------+ +--------------------------------+ p3-----> +-------------------------+ | | x = 3 | | | xref = 0x7ffde0450680 --|--+ +-------------------------+
稍微修改您的代码:
std::vector<Point> points;
for (int i = 0; i < 2; i++) {
Point p;
p.x = i;
points.push_back(p);
std::cout << i << " " << &p << "\n";
std::cout << &p.x << "(" << p.x << ") " << &p.xref << "(" << p.xref << ")\n";
std::cout << &points[0].x << "(" << points[0].x << ") " << &points[0].xref << "(" << points[0].xref << ")\n";
}
示例输出:
0 0x7fffa80e0840 0x7fffa80e0840(0) 0x7fffa80e0840(0) 0x55bd4ff52c30(0) 0x7fffa80e0840(0) //On each iteration p is in the same location 1 0x7fffa80e0840 //So p.x is in the same location 0x7fffa80e0840(1) 0x7fffa80e0840(1) //And the vector elements are initialised with p //So each element's xref is the same as p.x and p.xref 0x55bd4ff52c50(0) 0x7fffa80e0840(1)
最后,通过对 Point
构造函数进行一些控制,您可以使其按预期运行。
struct Point {
int x;
int& xref = x;
Point() = default;
Point(const Point& p): x(p.x) {}
};
以上代码执行以下操作:
- 它将默认的复制构造函数替换为仅从源复制
x
值的复制构造函数。因此xref
保留其默认初始化。 - 由于定义了您自己的构造函数,默认构造函数被删除。所以现在你需要显式地声明它;幸运的是,您可以指定默认实现就足够了。