【问题标题】:What is the most effective way to move items within a vector?在向量中移动项目的最有效方法是什么?
【发布时间】:2011-09-23 10:34:12
【问题描述】:

我见过一些特殊情况,可以使用 std::rotate 或与其中一种搜索算法结合使用,但通常情况下:当一个人有一个包含 N 个项目的向量并想要编写如下函数时:

void move( int from, int count, int to, std::vector<int>& numbers );

我一直在考虑创建一个新矢量 + std::copy 或插入/擦除的组合,但我不能说我最终得到了一些漂亮而优雅的解决方案。

【问题讨论】:

  • 这可能已经很明显了,但我仍然想指出,移动大块内存总是比移动小内存引用效率低。出于这个原因,使用例如执行您描述的操作会更有效地实现。 std::list 而不是 std::vector
  • 如果源区域和目标区域重叠,move 函数是否应该正常工作?
  • @FrerichRaabe:你说得对,这就是我必须处理的。是的,源和目标可以重叠。
  • 更新:嘿,这很尴尬,我去找指定功能的人并告诉我它们毕竟不能重叠。我想这会让事情变得容易得多。
  • rotate 最好的办法:stackoverflow.com/a/14580001/634821

标签: c++ stl


【解决方案1】:

在得出任何结论之前,先进行分析总是很重要的。 vector 的数据内存的连续性可能会提供基于节点的容器所没有的显着缓存优势。所以,也许你可以试试直接方法:

void move_range(size_t start, size_t length, size_t dst, std::vector<T> & v)
{
  const size_t final_dst = dst > start ? dst - length : dst;

  std::vector<T> tmp(v.begin() + start, v.begin() + start + length);
  v.erase(v.begin() + start, v.begin() + start + length);
  v.insert(v.begin() + final_dst, tmp.begin(), tmp.end());
}

在 C++11 中,您可以将第一行和第三行中的迭代器包装到 std::make_move_iterator

(要求dst不在[start, start + length)内,否则问题不明确。)

【讨论】:

  • 我认为您的“长度 - 开始 >= 0”条件不正确,否则我将被禁止从索引 10 复制 2 个元素。
  • @Miro:当然,这很愚蠢,让我来解决。我的意思是length &gt;= 0,但这是自动的。
  • 现在说得通了。我试图修正一个小错字(final_dst 行中的两个 ':'),但我没有足够的权限,所以请随意这样做。
  • @Miro:完成,谢谢!我确实错误地输入了三元条件,不知道为什么。
【解决方案2】:

根据向量的大小和所涉及的范围,这可能比执行复制/擦除/插入更便宜。

template <typename T>
void move_range(size_t start, size_t length, size_t dst, std::vector<T> & v)
{
    typename std::vector<T>::iterator first, middle, last;
    if (start < dst)
    {
        first  = v.begin() + start;
        middle = first + length;
        last   = v.begin() + dst;
    }
    else
    {
        first  = v.begin() + dst;
        middle = v.begin() + start;
        last   = middle + length;
    }
    std::rotate(first, middle, last);
}

(假设范围有效且不重叠。)

【讨论】:

  • 这很好用。我希望它在标准库中。也许是时候写一份提案了。
【解决方案3】:

Pre-C++11(尽管以下内容仍然有效)您可以获得更有效的“移动”,用于专门化/重载 std::swap 的包含类型。要利用这一点,您需要执行类似的操作

std::vector<Foo> new_vec;
Foo tmp;

for (/* each Foo&f in old_vec, first section */) {
    swap (f, tmp);
    new_vec .push_back (tmp);
}

for (/* each Foo&f in old_vec, second section */) {
    swap (f, tmp);
    new_vec .push_back (tmp);
}

for (/* each Foo&f in old_vec, third section */) {
    swap (f, tmp);
    new_vec .push_back (tmp);
}

swap (new_vec, old_vec);

如果 Foo 有一个移动操作符但没有专门化 swap,上述方法也可以为 C++11 提供良好的结果。

如果Foo 没有移动语义或其他优化的swap,链接列表或some clever sequence type 可能会更好地工作

还要注意,如果上面是在一个函数中

std::vector<Foo> move (std::vector<Foo> old_vec, ...)`

那么您可能能够执行整个操作without copying anything, even in C++98,但要使其工作,您需要通过引用 pass by valuenot,这与传统的首选传递方式背道而驰-参考智慧。

【讨论】:

  • 实际上可能建议为此致电std::swap。将swap(Foo&amp;, Foo&amp;) 放置在与Foo 相同的命名空间中是正常的,并且通过请求std::swap,您不会让ADL 找到正确的重载。
  • 您应该将自己的 swap 特化放入 std 命名空间。
  • 我可能弄错了,但 AFAIK 这不是经验法则。只允许专门化函数,但不能部分专门化函数。 - 任何高质量代码(包括标准库实现)都应该使用 ADL 来找到合适的 overload 进行交换。 - 顺便说一句,我根本看不到您的算法在哪里执行任何类型的旋转。它只是复制所有内容,然后按原样交换所有内容。
  • @spraff:专业化不是您可以选择放在任何地方的新声明。并且您不允许在 std 命名空间中添加重载。正确的做法是:(a)在您的类的同一命名空间中提供交换重载,以便 ADL 将其拾取并(b)如果可能,专门化 std::swap。 (b) 如果您正在处理类模板,则不可能 - 至少在 C++03 中是不可能的。至于调用swap,您应该执行以下操作:using std::swap; swap(x,y); 这样,ADL 被使用(这很好)和 std::swap 作为后备。这几乎就是 boost::swap 所做的,顺便说一句。
  • 从技术上讲,添加“部分专业化”实际上是添加新功能,我假设Foo 是一个完整的类型。顺便说一句,再看一遍,new_vec 的推送将在无论达到哪个部分的顺序,通常是不同的顺序。然后我们交换 whole 容器,保留新顺序但将其存储在 old_vec.
猜你喜欢
  • 1970-01-01
  • 2020-01-28
  • 1970-01-01
  • 2020-06-22
  • 2011-11-13
  • 1970-01-01
  • 2012-04-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多