When I am pushing a reference to an object from a vector in a map the previous value in the reference vector becomes garbage, but the original object doesn't.
Here is the minimal code that reproduces the problem:
#include <iostream>
#include <vector>
#include <map>
#include <string>
class foo
{
private:
std::map<std::string, std::vector<int>> _allObjs;
std::vector<int*> _someObjs;
public:
void addObj(const std::string &name, int obj)
{
_allObjs[name].push_back(obj);
_someObjs.push_back(&_allObjs[name].back());
}
void doStuff()
{
for (auto &obj : _someObjs)
{
std::cout << *obj << std::endl;
}
}
};
int main()
{
foo test;
test.addObj("test1", 5);
test.addObj("test1", 6);
test.addObj("test2", 7);
test.addObj("test2", 8);
test.doStuff();
}
Expected Output
5
6
7
8
Actual Output
-572662307
6
-572662307
8
When debugging it I found the pointer becomes garbage as soon as I push the object to _allObjs in addObj. I have no idea what is causing this, so I can't be much help there. Thanks!
A vector stores its data in a contiguous block of memory.
When you want to store more than it currently has capacity for, it will allocate a new, larger contiguous block of memory, and copy/move all the existing elements from the previous block of memory into the new one.
When you store pointers to your ints (&_allObjs[name].back()), you're storing the memory address of the int in one of these blocks of memory.
As soon as the vector grows to a size where it needs to create additional space, all these memory addresses will be pointing to deallocated addresses. Accessing them is undefined behaviour.
Let us see what this reference page says about inserting new objects to a vector:
If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.
So, when you add a new object, the previously stored pointers that refer to objects in that same vector may become invalid i.e. they no longer point to valid objects (unless you have made sure that capacity is not exceeded, which you didn't).
The pointers in your
std::vector<int*> _someObjs;
aren't stable.
When you use
_allObjs[name].push_back(obj);
any addresses obtained earlier might be invalidated due to reallocation.
As written in the reference:
If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.
As others have rightfully mentioned, adding items to a vector may be invalidating iterators and references on resizing of the vector.
If you want to quickly alleviate the situation with minimal code changes, change from a
std::map<std::string, std::vector<int>>
to
std::map<std::string, std::forward_list<int>>
or
std::map<std::string, std::list<int>>
Since a std::list / std::forward_list does not invalidate iterators and references when resizing the list, then the above scenario should work (the only iterators that will be invalidated are ones pointing to items that you've removed from the list).
Example using std::list
Note that the drawback is that usage of linked list will not store items in contiguous memory unlike std::vector, and std::list takes up more memory.
This: _allObjs[name].push_back(obj); (and the other push_back) potentially invalidates all iterators (and pointers) into the vector. You cannot assume anything about them afterwards.
Related
Let us say I create std::vector<int*> myVec; and reserve 100 entries and populate the vector with values so that all 100 elements have valid pointers. I then cache a pointer to one of the elements,
int * x = myVec[60];
Then, if I append another int * which triggers a resize along with a move due to heap fragmentation, does the previous pointer to a pointer become invalidated or does it point to the new location in memory?
If my memory servers me correct, if the example were to std::vector<int> myVecTwo with the same conditions as above and I stored a ptr like
int * x = &myVecTwo[60];
and proceeded to append and resize, that pointer would be invalided.
So, my question is as follows. Would the pointer to the pointer become invalidated? I am no longer certain because of the new C++ std functionality of is_trivially_copyable and whether the std::vector makes use of such functionality or POD checks.
Would the pointer by invalidated in C++11?
No.
As you showed, after the reallocation, the pointers to the elements of the vector, like int * x = &myVecTwo[60]; would be invalidated. After the reallocation, the elements themselves would be copied, but the object pointed by the element pointers won't be affected, then for int * x = myVec[60];, after the reallocation x is still pointing to the same object, which has nothing to do with the reallocation of the vector.
This question already has answers here:
Does insertion of elements in a vector damages a pointer to the vector?
(6 answers)
does a pointer to an element of a vector remain after adding to or removing from the vector (in c++)
(1 answer)
Closed 5 years ago.
I run into an issue which I don't quite understand:
I am creating an object Edge with
edge_vec1.push_back(Edge(src,dest));
Then I want to keep a pointer to this Edge in a separate vector:
edge_vec2.push_back(&edge_vec1.back());
However, once I add the second Edge object, the pointer to the first Edge in edge_vec2 is invalidated(gets some random data). Is it because the pointer in edge_vec2 actually points to some place in edge_vec1, and not the underlying element? I can avoid this by creating my Edge objects on the heap, but I'd like to understand what's going on.
Thank you.
When a new element is added to a vector then the vector can be reallocated. So the previous values of pointers to the elements of the vector can be invalid.
You should at first reserve enough memory for the vector preventing the reallocation.
edge_vec2.reserve( SomeMaxValue );
From http://en.cppreference.com/w/cpp/container/vector/push_back:
If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.
It's a bad idea to depend on pointers/references to objects in a vector when you are adding items to it. It is better to store the value of the index and then use the index to fetch the item from the vector.
edge_vec2.push_back(edge_vec1.size()-1);
Later, you can use:
edge_vec1[edge_vec2[i]]
for some valid value of i.
The requirements of std::vector are that the underlying storage is a continuous block of memory. As such, a vector has to reallocate all of its elements when you want to insert an element but the currently allocated block is not large enough to hold the additional element. When this happens, all iterators and pointers are invalidated, as the complete block is reallocated (moved) to a completely different part of memory.
The member function capacity can be used to query the maximum amount of elements which can be inserted without reallocating the underlying memory block. Examplary code querying this:
std::vector<int> vec;
for(int i = 0; i < 1000; i++) {
bool still_has_space = vec.capacity() > vec.size();
if (!still_has_space) std::cout << "Reallocating block\n";
vec.push_back(i);
}
In case the strong guarantee of contiguous memory layout is not need, you might be better of using std::deque instead of std::vector. It allows pushing elements on either end without moving around any other element. You trade this for slightly worse iteration speeds.
std::deque<int> deq;
std::vector<int*> pointers;
for(int i = 0; i < 1000; i++) {
deq.push_back(i);
pointers.push_back(&deq.back());
}
for(auto p : pointers) std::cout << *p << "\n"; // Valid
This question already has answers here:
Iterator invalidation rules for C++ containers
(6 answers)
Closed 6 years ago.
I filled a vector with A objects, then stored these objects address in a multimap [1], but the print message shows that the reference to the object stored in the vector changed [2]. Do you see why? and how avoid any changes.
//[1]
vector<A> vec;
multimap<const A*, const double > mymultimap;
for (const auto &a : A) {
double val = a.value();
vec.push_back(a);
mymultimap.insert(std::pair<const A*, const double >( &vel.back(), val));
// displaying addresses while storing them
cout<<"test1: "<<&vec.back()<<endl;
}
//[2]
// displaying addresses after storing them
for(auto &i : vec)
cout << "test2: " << &i <<endl;
Results:
test1: 0x7f6a13ab4000
test1: 0x7f6a140137c8
test2 :0x7f6a14013000
test2 :0x7f6a140137c8
You are calling vec.push_back(a) within your for loop. Therefore the vector may re-allocate the underlying array if it runs out of space. Therefore the address of the previous elements are no longer valid if they were copied to the new memory location.
For example say you allocated 3 elements and stored their addresses. After pushing back a 4th element the vector has to reallocate. That means the first 3 elements will be copied to a new location, then the 4th will be added after that. Therefore the address you had stored for the first 3 are now invalid.
Iterators (and references and object adresses) are not guaranteed to be preserved when you call vector<T>::push_back(). If the new size() is greater than current capacity(), a reallocation will happen and all the elements are moved or copied to the new location.
To avoid this, you can call reserve() before you start inserting.
One of the main features of std::vector is that it stores its elements in contiguous memory (which is great for performance when you visit vector's items on modern CPUs).
The downside of that is when the pre-allocated memory for the vector is full and you want to add a new item (e.g. calling vector::push_back()), the vector has to allocate another chunk of contiguous memory, and copy/move the data from the previous location to the new one. As a consequence of this re-allocation and copy/move, the address of the old items can change.
If for some reason you want to preserve the address of your objects, instead of storing instances of those objects inside std::vector, you may consider having a vector of pointers to objects. In this case, even after the reallocation, the object pointers won't change.
For example, you may use shared_ptr for both the vector's items and the multimap's key:
vector<shared_ptr<const A>> vec;
multimap<shared_ptr<const A>, const double> mymultimap;
Suppose I have the following:
struct Foo {
Foo () : bar(NULL), box(true) {}
Bar* bar;
bool box;
};
and I declare the following:
std::vector<Foo> vec(3);
I have a function right now which does something like this:
Foo& giveFoo() { //finds a certain foo and does return vec[i]; }
Then the caller passes along the address of the Foo it obtains by reference as a Foo* to some other guy. What I'm wondering, however, is if this pointer to Foo will remain valid after a vector grow is triggered in vec? If the existing Foo elements in vec are copied over then presumably the Foo* that was floating around will now be dangling? Is this the case or not? I'm debugging an application but cannot reproduce this.
Any pointers or references to elements will be invalidated when the vector is reallocated, just like any iterators are.
The pointer will remain valid until you call a non-const member function on the vector that:
causes its size to grow beyond its capacity (when that happens, the internal storage will
be reallocated and all pointers and references to the elements will be invalidated) or
inserts an element before the element that the pointer points to, or
deletes the element from the vector, or
deletes an element from the vector that was located before the element that the pointer points to.
The first two bullets can happen at the same time. The difference is that references/pointers to elements located before insertion point remain valid as long as the size doesn't exceed capacity.
Yes it may become invalid because basically when vector needs to increase it's reserved size, it just deletes it's internal storage (which is basically an array), allocates enlarged one and copies it's previous contents there.
If you sure that index stays the same though you may access desired data by using this index each time you need it.
Below is what the standard says about the validity of vector iterators for vector modifiers:
vector::push_back(), vector::insert(), vector::emplace_back(), vector::emplace():
Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.
vector::erase():
Invalidates iterators and references at or after the point of the erase.
Any assumption beyond that is not safe.
I basically have a
vector<Object> vec_;
as a class member in a cpp class. At a certain class function this vector will be filled with "Objects" just like this:
vec_.push_back(Object());
At a later time I iterate through the vector elements and keep a pointer to the best element. Then the vector is cleared as shown in the following:
Object* o_ptr = &(vec_[0]);
for (unsigned int i = 1; i < vec_.size(); i++) {
if (o_ptr->getCost() > vec_[i].getCost()) {
o_ptr = &(vec_[i]);
}
vec_.clear();
Now my question is: What happens to the objects removed from the vector? Is their lifetime over as soon as the they are removed from the vector? And does the pointer also point to empty space then?
And if not when does the lifetime of these objects end?
Regards scr
When vector.clear() is called (or when the vector is destructed) all objects contained with the vector will be destructed (as in this case they are objects and not raw pointers), leaving o_ptr as a dangling pointer.
Note that caching the address (or iterator) of an element in vector is dangerous, even without a call to clear(). For example, push_back() could result in internal reallocation of the vector, invalidating the cached address (or iterator).
What happens to the objects removed from the vector?
They are destructed.
Is their lifetime over as soon as the they are removed from the vector?
Yes.
Does the pointer also point to empty space then?
Yes it does. If you want to save the object, make a copy of it before clearing the vector.
It's also important to note that certain vector operations might leave pointers pointing to nothing even if the vector is not cleared. Specifically resizes, which typically involve a reallocation. It's generally best to store references to vector elements by index, since you will always be able to get the element via the index (whereas a pointer may be invalidated after certain operations).
The subscript operator, applied to vectors, returns a reference and does not make a copy.
The objects are owned by the vector and .clear() indeed removes them.
In addition, pointing to the objects stored in a std::vector is a bit dangerous. If you push new elements to the vector, at some point the vector might need to allocate more memory - which is likely to copy all the previous elements to a different address using their copy constructor (hence invalidating your pointers).
So: Use integer indices with std::vector instead of pointers, unless you know that you won't be pushing beyond the reserved capacity (which you can assure using .reserve()).
(Also while we're at it, make sure not to confuse the vector's internal buffer size with .size(), which is simply the number of actual elements stored).
The best way to track the lifetime of an object is to add a printf to both the constructor and destructor.
class MyObject
{
public:
MyObject()
{
printf("MyObject constructed, this=%p\n", this);
}
~MyObject()
{
printf("MyObject destructed, this=%p\n", this);
}
};