2

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  
Mathieu
  • 8,840
  • 7
  • 32
  • 45
cabe
  • 115
  • 2
  • 8

3 Answers3

7

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.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
4

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.

krzaq
  • 16,240
  • 4
  • 46
  • 61
  • Dear Krzaq, yes you are right and vec.reserve() worked well. Regards – cabe Oct 04 '16 at 15:00
  • I'm not a big fan of the `reserve()` method as if someone later calls `push_back()` it may invalidate "the world" of pointers. – Mr.C64 Oct 04 '16 at 15:09
3

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;
Mr.C64
  • 41,637
  • 14
  • 86
  • 162