我对C++如何在向量中管理对象感到困惑。 假设我执行以下操作:
vector<MyClass> myVector;
myVector.push_back(a);
myVector.push_back(b);
MyClass & c = myVector[1];
myVector.erase(myVector.begin());
参考文献c仍然有效吗(或者更好的是,它是否有保证有效)?如果无效,我是否必须始终从参考文献中复制以确保安全性?
与Java或C#的引用不同(更像C++指针而不是C++引用),C++中的引用与指针一样“愚笨”,这意味着如果你获取一个对象的引用,然后在内存中移动该对象,你的引用将不再有效。
引用c还有效吗(或者最好说,它是否保证有效)?
在您所描述的情况下,标准向量不能保证将其包含的对象在向量内容更改时保留在内存中的相同位置(删除项,调整向量大小等)。
这将使迭代器和指向所包含对象的指针/引用失效。
如果不行,我是否必须始终从引用中复制以确保安全性?
有多种方法可以继续“指向”正确的对象,所有这些方法都涉及一定级别的间接操作。
最简单的方法是对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引用)。
[5] 当向量的内存被重新分配时,它的迭代器被作废。 此外,在向量中间插入或删除元素使得指向插入或删除点后面的所有元素的迭代器失效。因此,如果使用reserve()预先分配向量将要使用的全部内存并且所有插入和删除都在向量的末尾进行,则可以防止向量的迭代器失效。
因此,在erase
之后,您的迭代器已经失效,不能再使用。
如果您更改向量的位置或大小,通常情况下,对向量内容的引用将变得无效:有一些例外情况。只有在您打算修改向量中数据的位置或向量中元素的大小或数量之前才需要复制数据。
参考 c 现在是否有效(或更好的是,是否保证有效)?
不,现在它处于未定义状态。
如果没有,我是否必须始终从引用中制作副本以确保安全?
如果在您检索和使用之间存在被删除或移动等可能性,则需要制作副本。