Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

copy algorithm with back_inserter

I do not understand why this code is accurate

vector<int> coll;
coll.reserve(2*coll.size());
copy (
  coll.begin(), coll.end(),    // zrodlo
  back_inserter(coll)          // przeznaczenie
);

coll.end() represents the end of vector. After I push_back anything (as back_insert_iterator does) what coll.end() returns is the same what was before or something different? Are there more than one terminating iterator? Why end() can be use as an end of container even when new content is added?

Moreover, you cannot apply the code to list container - it gets stuck. That is important because in case of vector push_back makes iterators unreliable after reallocating data (when size()==capacity() and push_back() called) whereas in case of list that's not the case. Than why the code hangs for list?

Edit: (sscce)

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

template <class T>
inline void PRINT_ELEMENTS (const T& coll, const char* optcstr="")
{
    typename T::const_iterator pos;

    std::cout << optcstr;
    for (pos=coll.begin(); pos!=coll.end(); ++pos) {
        std::cout << *pos << ' ';
    }
    std::cout << std::endl;
}

int main(){
  list<int> coll;

  list<int>::iterator end = coll.end();
  copy (
    coll.begin(), coll.end(),    // zrodlo
    back_inserter(coll)          // przeznaczenie
    );
  cout << *end << endl;
  PRINT_ELEMENTS(coll);
}
like image 246
lord.didger Avatar asked Jun 14 '13 08:06

lord.didger


People also ask

What is copy algorithm?

C++ Algorithm Function copy() C++ Algorithm copy() function is used to copy all the elements of the container [first,last] into a different container starting from result.

How does Back_inserter work in C++?

back_inserter() returns a back_insert_iterator , which has to function like an output iterator. Specifically, it has to support operations such as pre- and post increment, and dereference assignment. If it didn't support those operations, it couldn't be used where output iterators are required.

How back_ inserter works?

A back-insert iterator is a special type of output iterator designed to allow algorithms that usually overwrite elements (such as copy) to instead insert new elements automatically at the end of the container. Syntax: std::back_inserter (Container& x); x: Container in which new elements will be inserted at the end.

Why use back_ inserter c++?

The back_inserter provides a method in C++ Programming Language that constructs an iterator and does the operation of inserting new elements to a list through the end. Using back_inserter has the advantage that we don't need to have a proper number of the list.


2 Answers

coll.end() is called before the copying and back insertion begins, so essentially the code is the same as

coll.reserve(2*coll.size());
auto oldEnd = coll.end();
copy (coll.begin(), oldEnd, back_inserter(coll) ); 

meaning, copy will not re-evaluate coll.end(), so it will not notice/bother that it is inserting into the same vector, and that what once was the end of the vector is not the end after the first insertions.

The same algorithm will not compile for lists, because std::list has no reserve method. However, without reserve it should work for lists, since std::list::push_back does not invalidate iterators. You are right that std::vector::push_back invalidates iterators when reallocation occurs, so it's very important to do the reserve call, because it makes sure no reallocation is needed.

like image 143
Arne Mertz Avatar answered Sep 30 '22 10:09

Arne Mertz


the begin() and end() pointers/iterators used to determine what is to be copied are evaluated once when the function is called. So essentially std::copy() will increment it's cursor iterator which will eventually reach end(), because vector<T>::const_iterator is just a fancy T* pointer on an old school array.

As you correctly mentionned, if a push_back makes the vector reallocate and move the data somewhere else in memory, then the next element copied will have a wrong address for source, which is likely to end up with a segmentaion fault.

For a list, it can never terminate because end() is a sentinel/guard pointer, and you can only reach end() by incrementing the iterator on the last element of the list. So the address of end() itself never changes, but because you are constantly adding an element at the end of the list, you will never reach the last element, so std::copy() can never obtain a pointer to end(). Kind of like a dog chasing it's tail.

It's easier to understand with illustrations and diagrams, read-up on doubly linked list and sentinel nodes, it will all make sense.

like image 33
rectummelancolique Avatar answered Sep 30 '22 10:09

rectummelancolique