我将一些对象存储在矢量中。当我调用这样一个使用引用的对象的成员函数时,程序终止(没有错误)。我写了下面的代码做了一些测试。它在添加元素之后接缝,第一个条目中的引用失败。为什么这样做,我该怎么做才能避免这个问题呢?当我使用指针而不是引用时,它的行为完全相同。
#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
答案 0 :(得分:6)
这是因为你对emplace_back的调用导致向量调整大小。为了做到这一点,向量可能必须或可能不必将整个向量移动到存储器中的不同位置。您的“ref”仍然引用旧的内存位置。
这是否真的发生在某种程度上依赖于实现;编译器可以自由地为向量保留额外的内存,这样他们就不必在每次向后面添加内容时重新分配。
emplace_back的标准文档中提到了它:
迭代器有效期
如果发生重新分配,则所有迭代器,指针 与此容器相关的引用无效。除此以外, 只有结束迭代器失效,以及所有其他迭代器, 保证指针和元素引用不断引用 在电话会议之前他们指的是相同的元素。
为了避免这个问题,您可以(如评论中的JAB建议)动态创建引用,而不是将其存储为成员变量:
int& get_ref() {return var;}
...虽然我宁愿使用智能指针而不是这种东西。
或者,正如RnMss建议的那样,实现复制构造函数,以便每当对象被vector复制时它引用新位置:
A(A const& other) : ref(var) {
*this = other;
}
答案 1 :(得分:3)
好的,所以这里正在发生的事情。根据内存位置来理解对象真的很有帮助,并且记住允许向量在内存中移动对象。
v.emplace_back(5)
在向量中创建A对象。此对象现在位于从0x1234
到0x123C
的内存块中。成员变量 var 位于0x1234
,成员变量 ref 位于0x1238
。对于此对象, var 的值为0x0005
, ref 的值为0x1234
。
在向量中添加元素时,向量在第二次插入期间用完了空间。因此,它调整大小并将当前元素(此时只是第一个元素)从位置0x1234
移动到位置0x2000
。这意味着会员元素也会移动,因此 var 现在位于地址0x2000
, ref 现在位于0x2004
。但是他们的值被复制了,因此 var 的值仍为0x0005
,而 ref 的值仍为0x1234
。
ref 指向无效位置(但 var 仍包含正确的值!)。尝试访问内存 ref 现在指向未定义的行为,通常不好。
这样的事情是提供对成员属性的引用访问的一种更典型的方法:
int & get_ref() {return var;}
将引用作为成员属性本身并不是错误,但如果要存储对象的引用,则必须确保该对象不会移动。