请考虑以下代码片段:
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();
}
如果我们假设class X
实现了移动语义,那么我该如何高效地实现MoveAppend
?
请考虑以下代码片段:
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();
}
如果我们假设class X
实现了移动语义,那么我该如何高效地实现MoveAppend
?
只需做:
#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();
}
}
如果目标数组 dst
为空,从源数组 src
移动元素到 dst
可以达到效果——这样做的成本非常低廉,只需"窃取"被 src
封装的数组,使得 dst
指向它。dst
不为空,则向 dst
添加元素时将使用源数组 src
中的元素进行移动构造。调用std::move()
后,src
将不再是空的——它将包含被移动元素的 "僵尸"。这就是为什么仍然需要调用 clear()
的原因。我稍微更喜欢这个答案,胜过被接受的答案:
#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;
}
示例:
#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;
}
尝试略微改进@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)));
}
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!!
除非必须使用(例如:std::ifstream&&),否则不要将 &&
用于参数。
source
,另一个用于扩展destination
的大小。如果我们在const&
和&&
上专门化append
函数(如Daniel的答案中所示),那么我们只需要进行一次向量内存分配,以扩展destination
的大小。 - Williamappend(src, dst)
),按值传递将保证两个向量分配:一个用于从调用者复制到我们的按值参数,另一个用于扩展目标向量的大小(然后通过从“源”向量移动来填充其内容)。如果调用者移动(append(move(src), dst)
),则只有一个分配:扩展目标的大小。 - William
dst.insert(end(dst), begin(src), end(src))
更好吗? - TemplateRexsrc
的相应元素复制构造dst
的新元素。OP想要的是移动。 - Andy Prowldst.insert(end(dst), make_move_iterator(begin(src)), make_move_iterator(end(src)));
可以翻译为:将src
中的元素移动到dst
的末尾,代码如下: - v.oddou