I'm confused about how C++ manages objects in vector. Say I do the following:
vector<MyClass> myVector;
myVector.push_back(a);
myVector.push_back(b);
MyClass & c = myVector[1];
myVector.erase(myVector.begin());
Is the reference c still valid (or better yet, is it guaranteed to be valid)? If not, do I have to always make copy from the reference to ensure safety?
Unlike Java or C# references (which are more like C++ pointers than C++ references), references in C++ are as "dumb" as pointers, meaning that if you get the reference of an object, and then you move that object in memory, your reference is not anymore valid.
Is the reference c still valid (or better yet, is it guaranteed to be valid)?
In the case you're describing, the standard vector is not guaranteed to keep the objects it contains at the same place in memory when the vector contents changes (removal of an item, resizing of the vector, etc.).
This will invalidate both iterators and pointer/references to the object contained.
If not, do I have to always make copy from the reference to ensure safety?
There is multiple ways to continue to "point" to the right objects, all of them implying a level of indirection.
The simplest is making a full copy of 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()) ;
The second simplest is to use a std::list
which is specifically designed for element insertion and removal, and will not change the contained objects, nor invalidate pointers, references or iterators to them:
list<MyClass> x ;
x.push_back(a) ;
x.push_back(b) ;
list<MyClass> it = x.begin() ;
++it ;
MyClass & c = *it ;
x.erase(x.begin()) ;
Another would be to make a std::vector<MyClass *>
, which would contain pointers to MyClass
instead of MyClass
objects. You will then be able to keep a pointer or a reference to the pointed object, with a slightly different notation (because of the extra indirection):
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
This is unsafe because there is no clear (as far as the compiler is concerned) owner of the objects a and b, meaning there is no clear piece of code responsible to deallocating them when they are not needed anymore (this is how memory leaks happen in C and C++)
So if you us this method, make sure the code is well encapsulated, and as small as possible to avoid maintenance surprises.
Something better would be using smart pointers. For example, using C++11's (or 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
Now, if you use shared_ptr
, and know nothing about weak_ptr
, you have a problem, so you should close that gap.
Another solution would be to use C++11's unique_ptr
, which is the exclusive owner of the pointed object. So if you want to have a pointer or a reference to the pointer object, you will have to use raw pointers:
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
Note here that the vector is the unique owner of the objects, unlike the case above with the smart_ptr
.
You are coding in C++, meaning you have to choose the right method for your problem.
But first, you want to be sure to understand the level of indirection added by pointers, what pointers do, and what C++ references do (and why they aren't C#/Java references).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With