简体   繁体   中英

push_backs to a std::vector<std::reference_wrapper<type>>

Consider this attempt at push_back s to a std::vector of std::reference_wrapper s:

#include <iostream>
#include <vector>
#include <functional>
int main()
{
  std::vector<int> v_i;
  std::vector<std::reference_wrapper<int>> s_i;
  for(int i=0;i<10;++i)
  {
    v_i.push_back(i);
    s_i.push_back(std::ref(v_i[i]));
  }
  std::cout<<v_i[0]<<std::endl;
  std::cout<<s_i[0].get()<<std::endl;
  return -1;
}

I expect the [] operator to return a reference to the i-th element of v , and from the possible implementation given here , we can reasonably assume that the std::reference_wrapper object that is appended to s_i holds a copy of the pointer that points to the correct address of v_i[i] . However, the output of the code above is

0
1980603512 //or some other random garbage value

So obviously, the std::reference_wrapper s that are constructed inside the loop are pointing to temporary objects. Why does this happen, and what is the correct way of appending to s_i ?

By the way, I am using g++ 5.4.0 (with the -std=c++0x flag).

we can reasonably assume that the std::reference_wrapper object that is appended to s_i holds a copy of the pointer that points to the correct address of v_i[i]

No, you can't reasonably assume that. This is because any

v_i.push_back(i);

can result in reallocation of v_i ; and in fact you're almost guaranteed that this is going to happen here, at some point. And any reallocation automatically, and immediately, invalidates all existing iterators and pointers to the existing contents of a vector.

This is no different if you saved any plain pointers to some elements in the std::vector , then attempted to push_back() , causing reallocation and invalidation of those pointers. Any subsequent dereference of those pointers results in undefined behavior.

What the shown code is doing here is substantively equivalent, just using an extra onion layer of std::ref to wrap the whole thing, but resulting in logically equivalent undefined behavior.

If you wish to avoid undefined behavior here, the only practical way to do so is to reserve() the vector sufficiently, as to guarantee that no reallocation will happen by the subsequent push_back ()s.

The thing is that push_back operation potentially invalidates iterators\\references\\pointers to the std::vector elements.

From 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.

As your std::vector is growing, there is a moment in time when the new size is greater then the current capacity. As described above, it leads to the invalidation of references\\pointers to the std::vector elements.

In this particular case, std::reference_wrapper s are invalidated.

Further usage of invalidated std::reference_wrapper s causes UB .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM