Reputation: 50228
Consider this snippet:
class X;
void MoveAppend(vector<X>& src, vector<X>& dst) {
dst.reserve(dst.size() + src.size());
for (const X& x : src) dst.push_back(x);
src.clear();
}
If we assume that class X
implements move semantics, how can I efficiently implement MoveAppend
?
Upvotes: 38
Views: 24000
Reputation: 3018
Just trying to improve slightly the answer of @Daniel: the function should not be defined twice, the source should be passed by value.
// std::vector<T>&& src - src MUST be an rvalue reference
// std::vector<T> src - src MUST NOT, but MAY be an rvalue reference
template <typename T>
inline void append(std::vector<T> source, std::vector<T>& destination)
{
if (destination.empty())
destination = std::move(source);
else
destination.insert(std::end(destination),
std::make_move_iterator(std::begin(source)),
std::make_move_iterator(std::end(source)));
}
Now the caller can decide whether to copy or move.
std::vector<int> source {1,2,3,4,5};
std::vector<int> destination {0};
auto v1 = append<int>(source,destination); // copied once
auto v2 = append<int>(std::move(source),destination); // copied 0 times!!
Never use &&
for arguments, unless you have to (for example: std::ifstream&&).
Upvotes: 6
Reputation: 8431
I would slightly prefer this to the accepted answer:
#include <vector>
#include <iterator>
#include <utility>
template <typename T>
typename std::vector<T>::iterator append(const std::vector<T>& src, std::vector<T>& dest)
{
typename std::vector<T>::iterator result;
if (dest.empty()) {
dest = src;
result = std::begin(dest);
} else {
result = dest.insert(std::end(dest), std::cbegin(src), std::cend(src));
}
return result;
}
template <typename T>
typename std::vector<T>::iterator append(std::vector<T>&& src, std::vector<T>& dest)
{
typename std::vector<T>::iterator result;
if (dest.empty()) {
dest = std::move(src);
result = std::begin(dest);
} else {
result = dest.insert(std::end(dest),
std::make_move_iterator(std::begin(src)),
std::make_move_iterator(std::end(src)));
}
src.clear();
src.shrink_to_fit();
return result;
}
Example:
#include <string>
#include <algorithm>
#include <iostream>
int main()
{
const std::vector<std::string> v1 {"world", "!"};
std::vector<std::string> v2 {" "}, v3 {"hello"}, v4 {};
append(v1, v2); // copies
append(std::move(v2), v3); // moves
append(std::move(v3), v4); // moves
std::copy(std::cbegin(v4), std::cend(v4), std::ostream_iterator<std::string> {std::cout});
std::cout << std::endl;
}
Upvotes: 23
Reputation: 126412
Just do:
#include <iterator>
#include <algorithm>
// ...
void MoveAppend(std::vector<X>& src, std::vector<X>& dst)
{
if (dst.empty())
{
dst = std::move(src);
}
else
{
dst.reserve(dst.size() + src.size());
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
src.clear();
}
}
If dst
is empty, a move-assignment from src
to dst
will do the job - that will be as cheap as it can be, just "stealing" the array encapsulated by src
so that dst
will point to it afterwards.
If dst
is not empty, elements appended to dst
will be move-constructed from elements in src
. After the call to std::move()
, src
will not be empty - it will contain "zombie" moved-from elements. That's why the call to clear()
is still necessary.
Upvotes: 59