Inspired by this question, asking how to append a vector to itself, my first thought was the following (and yes, I realize insert is a better option now):
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
int main() {
std::vector<int> vec {1, 2, 3};
std::copy (std::begin (vec), std::end (vec), std::back_inserter (vec));
for (const auto &v : vec)
std::cout << v << ' ';
}
However, this prints:
1 2 3 1 * 3
The * is a different number every time the program is run. The fact that it’s only the 2 being replaced is peculiar, and if there actually is an explanation for that, I’d be interested to hear it. Continuing, if I append to a different vector (a copy of the original), it outputs correctly. It also outputs correctly if I add the following line before the copy one:
vec.reserve (2 * vec.size());
I was under the impression std::back_inserter was a safe way to add elements onto the end of a container, despite not reserving memory beforehand. If my understanding is correct, what’s wrong with the copying line?
I assume it’s nothing to do with the compiler, but I’m using GCC 4.7.1.
std::back_insertercreates an inserting iterator that inserts elements into a container. Each time this iterator is dereferenced, it callspush_backon the container to append a new element to the container.For a
std::vectorcontainer, a call topush_backwherev.size() == v.capacity()will result in a reallocation: a new array is created to store the contents of the vector, its current contents are copied into the new array, and the old array is destroyed. Any iterators into the vector at this time are invalidated, meaning they can no longer be used.In your program, this includes the input range defined by
begin(vec)andend(vec)from which thecopyalgorithm is copying. The algorithm continues to use these iterators, even though they are invalidated, thus your program exhibits undefined behavior.Even if your container had sufficient capacity, its behavior would still be undefined: the specification states that, upon insertion, “if no reallocation happens, all the iterators and references before the insertion point remain valid” (C++11 §23.3.6.5/1).
The call to
push_backis equivalent to insertion at the end, so the end iterator (std::end(vec)) that you passed intostd::copyis invalidated after a single call topush_back. If the input range is nonempty, the program therefore exhibits undefined behavior.Note that the behavior of your program would be well-defined if you used a
std::deque<int>or astd::list<int>, because neither of those containers invalidates iterators when elements are appended.