如何使用c ++ 11移动语义将向量内容附加到另一个向量?

Łuk*_*Lew 21 c++ move-semantics c++11

请考虑以下代码段:

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();
}
Run Code Online (Sandbox Code Playgroud)

如果我们假设class X实现移动语义,我该如何有效地实现MoveAppend

And*_*owl 38

做就是了:

#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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果dst是空的,那么来自srcto 的移动分配dst将完成工作 - 这将尽可能便宜,只是"窃取"封装的数组,src以便dst之后指向它.

如果dst不为空,则追加的dst元素将从元素中移动构造src.调用之后std::move(),src将不会为空 - 它将包含"僵尸"移动元素.这就是为什么呼叫clear()仍然是必要的.

  • @TemplateRex `dst.insert(end(dst), make_move_iterator(begin(src)), make_move_iterator(end(src)));`。 (5认同)
  • @TemplateRex:这将从“src”的相应元素复制构造“dst”的新元素。OP想要的是移动 (2认同)

Dan*_*iel 15

我会稍微偏爱这个接受的答案:

#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;
}
Run Code Online (Sandbox Code Playgroud)

例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

  • @Daniel调用者不应该对移动对象的状态有任何期待.移动的对象被视为无效,不应以任何方式使用. (4认同)
  • 为什么要"清理"右值源?我认为那是在不请自来的额外工作. (2认同)
  • 这个函数不应该有 2 个版本。源应该通过值传递,而不是通过 const ref 或右值。如果您按值传递源,则调用者可以决定是移动还是复制。 (2认同)

MFn*_*Fnx 6

只是试图稍微改进@Daniel 的答案:函数不应定义两次,源应按值传递。

// 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)));
}
Run Code Online (Sandbox Code Playgroud)

现在调用者可以决定是复制还是移动。

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!!
Run Code Online (Sandbox Code Playgroud)

切勿使用&&for 参数,除非您必须使用(例如:std::ifstream&&)。

  • 在这种情况下,有两种向量内存分配:一种用于复制“源”,另一种用于扩展“目标”的大小。如果我们将“append”函数专门用于“const&amp;”和“&amp;&amp;”(如丹尼尔的回答),那么我们只进行一次向量内存分配,以扩展“destination”的大小 (2认同)
  • @MFnx 如果调用者没有移入 (`append(src, dst)`),按值获取将保证两个向量分配:一个从调用者复制到我们的按值参数中,第二个用于扩展目标向量(然后通过从“源”向量移动来填充其内容)。如果调用者确实移入 (`append(move(src), dst)`),则只有一种分配:扩展目标的大小。 (2认同)
  • @MFnx 我一直想说的是,在调用者想要复制的情况下,让函数按值获取向量意味着 2 个向量分配,而通过 const&amp; 获取向量意味着只有 1 个向量分配。希望现在很清楚:) (2认同)