有没有一种标准的方法将范围移动到向量中?

72

考虑下面的程序,它向一个向量中插入一系列元素:

vector<string> v1;
vector<string> v2;

v1.push_back("one");
v1.push_back("two");
v1.push_back("three");

v2.push_back("four");
v2.push_back("five");
v2.push_back("six");

v1.insert(v1.end(), v2.begin(), v2.end());

这段代码高效地复制了一个范围,为目标向量分配足够的空间以容纳整个范围,因此最多只需要一个调整大小的操作。现在考虑以下尝试将范围移动到向量中的程序:

vector<string> v1;
vector<string> v2;

v1.push_back("one");
v1.push_back("two");
v1.push_back("three");

v2.push_back("four");
v2.push_back("five");
v2.push_back("six");

for_each ( v2.begin(), v2.end(), [&v1]( string & s )
{
    v1.emplace_back(std::move(s));
});

这个操作可以成功移动数据,但是并没有像insert()一样预分配目标向量的空间,所以在操作期间向量可能会被多次调整大小。

因此我的问题是,是否有一个与insert()等效的函数可以将一个范围移动到向量中?


1
如果您需要在向量中预分配空间,请使用std::vector::reserve,并保持push_back/emplace_back - rubenvb
这将是一种可选的优化,仅在范围由随机访问迭代器定义时才可能。不要指望它。 - Ben Voigt
@rubenvb 是的,我想那可能是答案,只是很遗憾没有像insert()一样干净的方法。 - Benj
当使用std::vector时,这总是成立的,不是吗? - Benj
@Benj:std::vector 迭代器是随机访问的,但库可能不包括优化。而且问题似乎是关于从任意未指定范围插入到向量中,这可能没有随机访问迭代器。 - Ben Voigt
2个回答

110
您可以使用 move_iteratorinsert 进行操作:
v1.insert(v1.end(), make_move_iterator(v2.begin()), make_move_iterator(v2.end()));

24.5.3节中的示例与此几乎完全相同。

如果(a)vector::insert使用迭代器标签分派来检测随机访问迭代器并预先计算大小(您在复制示例中假定了它),并且(b)move_iterator保留其包装的迭代器的迭代器类别(这是标准所要求的),则将获得所需的优化。

一个不太明显的点:我非常确定vector::insert可以从源中插入(这与本例无关,因为源和目标类型相同,因此就像复制/移动一样,插入也是相同的,但对于否则相同的示例而言会很重要)。尚未找到该要求进行如此操作的说明,我只是从传递给insert的迭代器对上对解引用之后可通过T进行原位构造这一要求中推出。


5
太棒了,我之前不知道make_move_iterator这个函数。 - Benj
5
嗯,我发现使用make_move_iterator可以将std::copy_if转换为等效的std::move_if。非常方便。 - Benj
1
@Evgeny:对我来说可行,所以我可能误解了您的意思。我建议您提出一个新问题,包括对你不起作用的代码。 - Steve Jessop
#include <iostream> #include <iterator> #include <memory> #include <vector>int main() { std::vector<std::unique_ptr<int>> src; src.emplace_back(std::make_unique<int>(10)); std::vector<std::unique_ptr<int>> dst; dst.insert(dst.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())); for (std::unique_ptr<int> &p : dst) { std::cout << *p << '\n'; } std::cout << src.size() << ' ' << dst.size() << '\n'; } - Steve Jessop
1
@EvgenyDanilenko:是的,const unique_ptr相当棘手。无法复制和移动。据我所知,它将带着它的引用一起进入坟墓,尽管我可能错过了一些聪明的技巧。 - Steve Jessop
显示剩余3条评论

44
  1. std::move algorithm with preallocation:

    #include <iterator>
    #include <algorithm>
    
    v1.reserve(v1.size() + v2.size()); // optional
    std::move(v2.begin(), v2.end(), std::back_inserter(v1));
    
  2. The following would be more flexible yet:

    v1.insert(v1.end(), 
         std::make_move_iterator(v2.begin()), 
         std::make_move_iterator(v2.end()));
    

    Steve Jessop provided background information on precisely what it does and probably how it does so.


2
哦,很棒,我不知道有这种形式的std::move。虽然我猜back_inserter仍然可能会导致多次调整大小。 - Benj
4
我认为第一个不现实的是只能重新分配一次:move可以看到你有一个随机访问迭代器,因此它可以计算出所需的大小,但它只看到了一个 back_insert_iterator,而不是底层的向量,因此它无法预留空间。这将需要对std::move进行极其复杂的重载才能捕捉到这种情况。 - Steve Jessop
@Ben Voigt:有点小疑问,move不能检测其目标迭代器上的preallocate并调用它,因为某些用户自定义迭代器可能会有完全不相关的preallocate函数(鸭子类型失效)。不过,它可以检测__preallocate,所以这不是问题。我同意你的观点,即检测迭代器上的彩蛋功能比像我建议的那样检测类型更好。 - Steve Jessop
@SteveJessop和@BenV - 我明白你们的意思了,我有点糊涂了。现在会进行编辑。 - sehe
@SteveJessop 没关系。实际上,我并不是在猜测,但移动语义经常会让我犯错,所以我不想做出虚假的声明。我当时忙于其他事情,所以无法验证。 - sehe
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接