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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在将另一个实例添加到同一向量后引用的成员变量的引用相关的知识,希望对你有一定的参考价值。

请考虑以下代码:

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

答案

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

复制引用(即成员)时,新引用将引用与复制引用相同的对象。因此,如果你将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的移动完全相同),所以这是一个无关紧要的细节。

另一答案

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

this
另一答案

我怀疑你可能对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) {} }; 保留其默认初始化。
  • 作为定义自己的构造函数的结果,将删除默认构造函数。所以现在你需要明确声明它;幸运的是,您可以指定默认实现就足够了。

以上是关于在将另一个实例添加到同一向量后引用的成员变量的引用的主要内容,如果未能解决你的问题,请参考以下文章

C++ 在向量中存储对类成员的引用

Java String引用传递问题

如何声明对同一类中成员的引用?

包含对其自身实例的引用的标准向量的模板类

js中的垃圾回收机制

谨慎重载clone方法