How to use c++11 move semantics to append vector c

2019-02-06 00:07发布

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?

3条回答
再贱就再见
2楼-- · 2019-02-06 00:45

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&&).

查看更多
Rolldiameter
3楼-- · 2019-02-06 00:52

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.

查看更多
Anthone
4楼-- · 2019-02-06 01:08

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;
}
查看更多
登录 后发表回答