在将另一个实例添加到同一向量后引用的成员变量的引用

问题描述 投票:2回答:3

请考虑以下代码:

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

每个元素的qazxsw poi似乎都引用了最后添加的元素qazxsw poi而不是它自己的,如下所示:xref

c++ reference stdvector
3个回答
5
投票

将对象推入向量时,可以复制†。

复制引用(即成员)时,新引用将引用与复制引用相同的对象。因此,如果你将x复制到https://pastebin.com/H4wCszxp,那么Point p将引用Point copy,因为这就是copy::xref所指的。

因此,矢量内的所有p.x对象都是在循环范围内构造的p.xref自动变量的副本。因此,向量中的所有这些对象都引用了Point对象,它们是自动变量Point p的成员。他们都没有提到他们自己的int成员。

在第一次迭代期间,这很好,因为p指的是现有的x,它也具有与points[0].xref相同的值。但是在该迭代结束时,自动变量p.x(其成员points[0].x所指的)被销毁。在这一点上,p是一个悬垂的引用,不再引用有效的对象。在下一次迭代中,使用引用。使用悬空引用具有未定义的行为。


如果要访问此对象,请使用points[0].xref指针。如果要存储对象的引用,则不要指望该引用的副本引用另一个对象。避免将引用(或指针)存储到比保存引用的对象具有更短生命周期的对象。


†...或推动右值时移动。你没有推rvalue,副本与points[0].xref的移动完全相同),所以这是一个无关紧要的细节。


2
投票

发生这种情况是因为当将点插入到矢量中时,它会被复制,并且参考指向原始值。例如。这项工作很好:

this

2
投票

我怀疑你可能对Point宣言的工作方式有一点误解。它只是一个默认的初始化。

  • 它不保证int main() { Point p; p.x = 100; assert (p.x == p.xref); return 0; } 总是被构造为引用int& xref = x;。默认的复制构造函数将复制源实例的外部参照。
  • 此外,您可以使用初始化列表更改外部参照绑定。

我将首先使用您所看到的内容的简化版本进行说明,然后使用修改后的代码版本进行跟进。我将对你的xref对象做一个调整,让它按照你的预期运行。

x

样本输出:

//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 --|--+
         +-------------------------+  

稍微修改您的代码:

Point

样本输出:

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)

最后,通过对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; } 构造函数进行一些控制,您可以使其按照您的预期运行。

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";
}

上面的代码执行以下操作:

  • 它将默认复制构造函数替换为仅从源复制Point值的复制构造函数。因此struct Point { int x; int& xref = x; Point() = default; Point(const Point& p): x(p.x) {} }; 保留其默认初始化。
  • 作为定义自己的构造函数的结果,将删除默认构造函数。所以现在你需要明确声明它;幸运的是,您可以指定默认实现就足够了。
© www.soinside.com 2019 - 2024. All rights reserved.