13

我对 C++ 如何管理向量中的对象感到困惑。假设我执行以下操作:

vector<MyClass> myVector;
myVector.push_back(a);
myVector.push_back(b);
MyClass & c = myVector[1];
myVector.erase(myVector.begin());

引用 c 是否仍然有效(或者更好的是,它是否保证有效)?如果没有,我是否必须始终从参考中复制以确保安全?

4

4 回答 4

12

与 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专门为元素插入和删除而设计的 a,它不会更改包含的对象,也不会使指向它们的指针、引用或迭代器无效:

list<MyClass> x ;
x.push_back(a) ;
x.push_back(b) ;
list<MyClass> it = x.begin() ;
++it ;
MyClass & c = *it ;
x.erase(x.begin()) ;

使用指针(不安全)

另一种方法是制作 a 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's)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,那么您就会遇到问题,因此您应该缩小差距。

使用智能指针 2(更安全)

另一种解决方案是使用 C++11's 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 引用)。

于 2012-10-28T19:58:49.103 回答
10

参考vector

[5] 向量的迭代器在其内存被重新分配时失效。此外,在向量中间插入或删除元素会使指向插入或删除点之后的元素的所有迭代器无效。因此,如果您使用 reserve() 预分配向量将使用的尽可能多的内存,并且所有插入和删除都在向量的末尾,则可以防止向量的迭代器失效。

因此,您的迭代器无效erase,并且不能在and 之后使用。

于 2012-10-28T19:36:48.057 回答
3

如果您通常更改矢量的位置或大小,则对矢量内容的引用无效:有一些例外。如果您打算在使用数据之前修改向量中数据的位置,或者向量中元素的大小或数量,则只需进行复制。

于 2012-10-28T19:35:02.310 回答
3

引用 c 是否仍然有效(或者更好的是,它是否保证有效)?

不,它现在处于未定义状态。

如果没有,我是否必须始终从参考中复制以确保安全?

如果在您检索它和使用它之间有可能它被删除或移动(等),那么是的,您确实需要制作一份副本。

于 2012-10-28T19:35:49.740 回答