Miro Kropacek
Miro Kropacek

Reputation: 2886

What is the most effective way to move items within a vector?

I've seen some special cases where std::rotate could be used or a combination with one of the search algorithms but generally: when one has a vector of N items and wants to code function like:

void move( int from, int count, int to, std::vector<int>& numbers );

I've been thinking about creation of a new vector + std::copy or combination of insert/erase but I can't say I ended up with some nice and elegant solution.

Upvotes: 20

Views: 23321

Answers (3)

Blastfurnace
Blastfurnace

Reputation: 18652

Depending on the size of the vector and the ranges involved, this might be less expensive than performing copy/erase/insert.

template <typename T>
void move_range(size_t start, size_t length, size_t dst, std::vector<T> & v)
{
    typename std::vector<T>::iterator first, middle, last;
    if (start < dst)
    {
        first  = v.begin() + start;
        middle = first + length;
        last   = v.begin() + dst;
    }
    else
    {
        first  = v.begin() + dst;
        middle = v.begin() + start;
        last   = middle + length;
    }
    std::rotate(first, middle, last);
}

(This assumes the ranges are valid and they don't overlap.)

Upvotes: 9

spraff
spraff

Reputation: 33405

Pre-C++11 (although the following remains valid) you can get more efficient "moves" for contained types which specialise/overload std::swap. To take advantage of this, you would need to do something like

std::vector<Foo> new_vec;
Foo tmp;

for (/* each Foo&f in old_vec, first section */) {
    swap (f, tmp);
    new_vec .push_back (tmp);
}

for (/* each Foo&f in old_vec, second section */) {
    swap (f, tmp);
    new_vec .push_back (tmp);
}

for (/* each Foo&f in old_vec, third section */) {
    swap (f, tmp);
    new_vec .push_back (tmp);
}

swap (new_vec, old_vec);

The above may also give good results for C++11 if Foo has a move-operator but hasn't specialised swap.

Linked lists or some clever sequence type might work out better if Foo doesn't have move semantics or an otherwise-optimised swap

Note also that if the above is in a function

std::vector<Foo> move (std::vector<Foo> old_vec, ...)`

then you might be able to perform the whole operation without copying anything, even in C++98 but for this to work you will need to pass by value and not by reference, which goes against the conventional prefer-pass-by-reference wisdom.

Upvotes: 2

Kerrek SB
Kerrek SB

Reputation: 477168

It's always important to profile before jumping to any conclusions. The contiguity of vector's data memory may offer significant caching benefits that node-based containers don't. So, perhaps you could give the direct approach a try:

void move_range(size_t start, size_t length, size_t dst, std::vector<T> & v)
{
  const size_t final_dst = dst > start ? dst - length : dst;

  std::vector<T> tmp(v.begin() + start, v.begin() + start + length);
  v.erase(v.begin() + start, v.begin() + start + length);
  v.insert(v.begin() + final_dst, tmp.begin(), tmp.end());
}

In C++11, you'd wrap the iterators in the first and third line into std::make_move_iterator.

(The requirement is that dst not lie within [start, start + length), or otherwise the problem is not well-defined.)

Upvotes: 11

Related Questions