我对C ++如何管理向量中的对象感到困惑。说我做以下事情:
vector<MyClass> myVector;
myVector.push_back(a);
myVector.push_back(b);
MyClass & c = myVector[1];
myVector.erase(myVector.begin());
引用c是否仍然有效(或者更好,是否保证有效)?如果没有,我是否必须始终从参考文件中复制以确保安全?
答案 0 :(得分:12)
与Java或C#引用(更像是C ++引用而不是C ++引用)不同,C ++中的引用与指针一样“哑”,这意味着如果获得对象的引用,然后将该对象移动到内存中,你的参考不再有效。
引用c是否仍然有效(或者更好,是否保证有效)?
在您描述的情况下,当矢量内容发生变化(删除项目,调整矢量大小等)时,不保证标准矢量将其包含的对象保留在内存中的相同位置。 / p>
这将使迭代器和对所包含对象的指针/引用无效。
如果没有,我是否必须始终从参考文件中复制以确保安全?
有多种方法可以继续“指向”正确的对象,所有这些方法都意味着一个间接层。
最简单的是制作MyClass的完整副本:
vector<MyClass> x ;
x.push_back(a) ;
x.push_back(b) ;
MyClass c = x[1] ; // c is a full copy of b, not a reference to b
x.erase(x.begin()) ;
第二个最简单的方法是使用专为元素插入和删除而设计的std::list
,并且不会更改包含的对象,也不会使指针,引用或迭代器无效:
list<MyClass> x ;
x.push_back(a) ;
x.push_back(b) ;
list<MyClass> it = x.begin() ;
++it ;
MyClass & c = *it ;
x.erase(x.begin()) ;
另一个是制作std::vector<MyClass *>
,其中包含指向MyClass
而非MyClass
个对象的指针。然后,您将能够保持指向对象或指向对象的引用,并使用略有不同的表示法(因为额外的间接):
vector<MyClass *> x;
x.push_back(a); // a being a MyClass *
x.push_back(b); // b being a MyClass *
MyClass * c = x[1]; // c points to the same object as b
x.erase(x.begin()); // note that a will still need separate deallocation
这是不安全的,因为没有明确的(就编译器而言)对象a和b的所有者,这意味着没有明确的代码负责在不再需要它们时解除分配它们(这就是如何内存泄漏发生在C和C ++中)
因此,如果您使用此方法,请确保代码已完好封装,并尽可能小以避免维护意外。
更好的方法是使用智能指针。例如,使用C ++ 11(或boost)shared_ptr
:
vector< shared_ptr<MyClass> > x;
x.push_back(a); // a being a shared_ptr<MyClass>
x.push_back(b); // b being a shared_ptr<MyClass>
shared_ptr<MyClass> c = x[1]; // c points to the same object as b
x.erase(x.begin()); // No deallocation problem
现在,如果您使用shared_ptr
,并且对weak_ptr
一无所知,那么您就会遇到问题,因此您应该缩小差距。
另一种解决方案是使用C ++ 11的unique_ptr
,它是指向对象的独占所有者。因此,如果您想要指针或对指针对象的引用,则必须使用原始指针:
vector< unique_ptr<MyClass> > x;
x.push_back(a); // a being a unique_ptr<MyClass>
x.push_back(b); // b being a unique_ptr<MyClass>
MyClass * c = x[1].get(); // c points to the same object as b
x.erase(x.begin()); // No deallocation problem
请注意,向量是对象的唯一所有者,与上面smart_ptr
的情况不同。
您使用C ++进行编码,这意味着您必须为您的问题选择正确的方法。
但首先,您要确保了解指针添加的间接级别,指针的作用以及C ++引用的作用(以及它们不是C#/ Java引用的原因)。
答案 1 :(得分:10)
[5]当重新分配内存时,向量的迭代器无效。此外,在向量中间插入或删除元素会使指向插入或删除点后面的元素的所有迭代器无效。因此,如果使用reserve()预分配与向量将使用的内存一样多的内存,并且所有插入和删除都在向量的末尾,则可以防止向量的迭代器失效。
因此,您的迭代器无效且在erase
之后,且不得使用。
答案 2 :(得分:3)
如果通常更改向量的位置或大小,则对向量内容的引用无效:有一些例外。如果您打算在使用数据之前修改向量中数据的位置,或向量中元素的大小或数量,则只需制作副本。
答案 3 :(得分:3)
引用c是否仍然有效(或者更好,是否保证有效)?
不,它现在处于未定义的状态。
如果没有,我是否必须始终从参考文件中复制以确保安全?
如果在您检索它和使用它之间有可能被删除或移动(等),那么您需要复制。