包含引用或指针的对象向量

Vector of objects containing references or pointers

本文关键字:对象 向量 指针 引用 包含      更新时间:2023-10-16

我将一些对象存储在vector中。当我调用使用引用的这样一个对象的成员函数时,程序被终止(没有错误)。我编写了以下代码来运行一些测试。似乎在添加元素之后,第一个条目中的引用失败了。为什么会这样,我能做些什么来避免这个问题?当我使用指针而不是引用时,它的行为完全相同。

#include <iostream>
#include <vector>
using namespace std;
class A{
public:
    A(int i) : var(i), ref(var) {}
    int get_var() {return var;}
    int get_ref() {return ref;}
private:
    int var;
    int& ref;
};
int main ()
{
    vector<A> v;
    for(unsigned int i=0;i<=2 ;i++){
        v.emplace_back(i+5);
        cout<<"entry "<<i<<":"<<endl;
        cout<<"  var="<<v.at(i).get_var()<<endl;
        cout<<"  ref="<<v.at(i).get_ref()<<endl;
    }
    cout<<endl;
    for(unsigned int i=0;i<=2 ;i++){
        cout<<"entry "<<i<<":"<<endl;
        cout<<"  var="<<v.at(i).get_var()<<endl;
        cout<<"  ref="<<v.at(i).get_ref()<<endl;
    }
    return 0;
} 

输出为:

entry 0:
  var=5
  ref=5
entry 1:
  var=6
  ref=6
entry 2:
  var=7
  ref=7
entry 0:
  var=5
  ref=0          /////////////here it happens!
entry 1:
  var=6
  ref=6
entry 2:
  var=7
  ref=7
v has 3 entries

这是因为你对emplace_back的调用导致向量调整大小。为了做到这一点,向量可能需要也可能不需要将整个向量移动到内存中的不同位置。您的"ref"仍然引用旧的内存位置。

这是否实际发生在某种程度上取决于实现;编译器可以自由地为vector保留额外的内存,这样它们就不必每次向vector的后面添加内容时都重新分配内存。

emplace_back:

的标准文档中有提到
<

迭代器有效性/strong>

如果发生了重新分配,所有的迭代器,指针与此容器相关的引用无效。否则,只有end迭代器失效,其他所有迭代器,对元素的指针和引用保证保持引用

为了避免这个问题,您可以(正如JAB在注释中建议的那样)动态创建引用,而不是将其存储为成员变量:

int& get_ref() {return var;}

…虽然我更愿意用智能指针,而不是这种东西。

或者,如RnMss所建议的,实现复制构造函数,以便在vector复制对象时引用新位置:

A(A const& other) : ref(var) {
    *this = other;
}

好的,这就是发生的事情。它确实有助于从内存位置的角度理解对象,并记住vector允许在内存中移动对象。

v.emplace_back(5)

在vector中创建一个a对象。这个对象现在驻留在从0x12340x123C的内存块中。成员变量var位于0x1234,成员变量ref位于0x1238。对于该对象,var的值为0x0005ref的值为0x1234

向vector中添加元素时,在第二次插入时vector空间耗尽。因此,它调整大小并将当前元素(此时只是第一个元素)从位置0x1234移动到位置0x2000。这意味着成员元素也被移动了,所以var现在位于地址0x2000ref现在位于地址0x2004。但是它们的值被复制了,所以var的值仍然是0x0005ref的值仍然是0x1234

ref指向一个无效的位置(但是var仍然包含正确的值!)尝试访问内存ref现在指向未定义的行为,通常是坏的。

提供对成员属性的引用访问的更典型的方法是:

int & get_ref() {return var;}

将引用作为成员属性本身并没有错误,但是如果你要存储一个对象的引用,你必须确保该对象不会移动。