2

I have a loop which I create a unique_ptr in and move it into a vector. I also need to store it in another vector so I store a reference of it in another vector using the end() method of the first vector.

For some reason I happen to have some invalid read errors which seem to affect the element before the last one of the list containing the references.

Here is a simple snippet to illustrate my sayings:

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    std::vector<std::unique_ptr<int>> other;
    std::vector<std::reference_wrapper<std::unique_ptr<int>>> vec;

    for (int it = 0; it < 2; ++it)
    {
        std::unique_ptr<int> ptr = std::make_unique<int>(it);
        other.push_back(std::move(ptr));
        vec.push_back(*(other.end() - 1));
    }

    for (auto &it : vec)
        std::cout << "Vec: " << *it.get() << std::endl;
    return 0;
}

This will output something like that:

$ ./a.out 
Vec: 35716160
Vec: 1

As you can see the first element holds garbage, I suspect pushing from the end() of the first vector causes the issue, but I can't seem to figure why.

Ra'Jiska
  • 979
  • 2
  • 11
  • 23
  • 5
    `std::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."_ https://en.cppreference.com/w/cpp/container/vector/push_back – Richard Critten Jun 27 '18 at 16:08
  • related/dupe: https://stackoverflow.com/questions/37991037/priority-queue-changes-its-content/37991143#37991143 – NathanOliver Jun 27 '18 at 16:09
  • @NathanOliver will `std::vector::reserve(some size)` will resolve this? Just for clarification. – JeJo Jun 27 '18 at 16:12
  • 1
    @JeJo Yes. The OP could also move to a `std::shared_ptr` – NathanOliver Jun 27 '18 at 16:13
  • Thanks for the explanation. Unfortunately I only have to use `std::_unique_ptr` because of project restrictions. – Ra'Jiska Jun 27 '18 at 16:15
  • 1
    To clarify, the same problem would exist with a reference to a `std::shared_ptr` -- only by replacing both vectors with plain `shared_ptr`s would things work as expected. – Cameron Jun 27 '18 at 16:17
  • @Cameron replacing only the first vector, maybe. Second could be as it is as it uses to store the refs. – JeJo Jun 27 '18 at 16:46

1 Answers1

4

You're taking a reference to an element stored in the vector. This is fine as long as the vector doesn't resize itself, since the storage location of the element will not change. But, as soon as the vec vector needs to resize to hold one more element than its capacity, it moves all its current elements over to a new backing memory block, and the references you took now refer to freed memory (i.e. garbage).

Cameron
  • 96,106
  • 25
  • 196
  • 225
  • Thank you very much for the explanation, I thought this was working like a linked list. – Ra'Jiska Jun 27 '18 at 16:12
  • 3
    In that case it's really worth reading the documentation linked in Richard's comment carefully, and/or getting a good book. Knowing what data structure you're using is generally a good idea. – Useless Jun 27 '18 at 16:14
  • One of the (standardized) features of a `std::vector` (and `std::string`, by the way) is that its storage is guaranteed to be contiguous. This gives it O(1) random access, and makes it handy as a replacement where fixed-size arrays are normally used, except with an unknown size. – Cameron Jun 27 '18 at 16:15