如何使用c ++ 11移动语义来明确避免复制

时间:2014-01-23 12:26:44

标签: c++ c++11 move-semantics

我有一条线,其中结构类型变量仅在一次计算中重复使用,其结果将被分配回变量本身。

    for (int i = 0; i < 100; i++)
        e = f(i,e); //how to avoid copying

下面列出了整个代码。我的问题是,

如何明确确保e内的数据不会多次复制。

移动语义是一个不错的选择吗?

如何使用move比较将e定义为普通的非常量引用?

#include <vector>
using namespace std;
struct arr {
   vector<int> data;
   int len;
};

arr f(int x,arr xs) { 
   xs.data.push_back(x);
   return xs;
}

arr g() {
    arr e;
    for (int i = 0; i < 100; i++)
        e = f(i,e); //how to avoid copying
    return e;
}
int main() { 
    auto res = g(); return 0;
}

---编辑---

最接近我要找的是关于更改为e = f(i,std::move(e))的评论。根据我的理解,它明确地告诉编译器不再需要(第二次出现)e并且可以采用它的资源,我想这是编译器无法推断并且必须被告知的东西。

我以这种方式编写示例代码的原因是我想避免在void f中使用非const引用,并且在不牺牲效率的情况下获得非副作用代码的良好外观/错觉。我认为移动语义给我们带来了一件事,对吧? (允许标准容器'复制'arround)。我伪造了len字段和struct来创建一个案例,因为stl容器已经拥有所有移动语义处理程序。

鉴于我迄今为止从答案中学到的东西,我的问题实际上归结为: 在代码中使用e = f(i,std::move(e))之前和之后,实际发生了多少vector个复制操作?是没有还是100?为什么?

我是否需要将f声明为arr f(int x,arr&& xs) {,为什么或为什么我不需要使用&&

5 个答案:

答案 0 :(得分:3)

  

如何明确确保e内的数据不会多次复制。

通过引用传递arr。

  

移动语义是一个不错的选择吗?

在这种情况下,这不是一个好的选择。您将结果分配回e(因此它可以工作)。问题是如果你在f中做了一些会导致失败的事情(抛出异常)。在这种情况下,您不仅会丢失所有更改,而且还会丢失所有e(因为它的值会移到f但不会被分配回来)。

  

如何使用move比较将e定义为普通的非常量引用?

使用移动意味着您可以制作高效的破坏性副本(操作后原件保留为空)。

答案 1 :(得分:1)

如果我理解了这个问题,你可以使用指针或参考

答案 2 :(得分:0)

  

如何明确确保e内的数据不被复制多次   时间结束了。

     

移动语义是一个不错的选择吗?

不,至少在这种情况下不是这样。您需要通过引用获取数组:

#include <algorithm>
#include <vector>
using namespace std;
struct arr {
   vector<int> data;
   int len;
};

void f(arr& a, int x) {
  a.data.push_back(x); 
}

arr g() {
    arr e;
    e.data.reserve(100);
    for (int i = 0; i < 100; i++)
        f(e, i);
    return e;
}

int main() { 
    auto res = g(); return 0;
}

如果您知道元素的数量(示例中为100),您可能希望为它们保留空间,这样会更有效。

答案 3 :(得分:0)

utnapistim的答案的补充,我为您提供了一个可以编写的替代结果,即使用C ++ 11语言和库特征填充具有n个连续整数值的向量。

鉴于您显然希望将vectorint分组,这似乎是为了存储数组的大小,我可以提出最短的版本,哪个是

#include <vector>
#include <algorithm>

struct arr {
  std::vector<int> data;
  int len;
};

int main() {
  arr a;
  std::generate_n(std::back_inserter(a.data), 100, [&]() -> int { return a.data.size (); });
  return 0;
}

围绕arr::data的构造没有办法。假设您无法估计要推送的元素数量,在推送之前调整大小也不是一个选项。尽管如此,您可以generate_n简单地push_back(通过使用std::back_inserter)n生成器函数生成的值(正如您所看到的,可以是一个整洁的小lambda )。实际上,您可以显着减少代码和问题。

上述解决方案避免了原始代码的多个问题:

a)您不必调用不必要的函数f() - 它只会执行push_back,您可以直接在g()中编写。定义和调用f()完全没必要,但最糟糕的是,每次要复制一个整数时,都要复制arr 。这意味着每次复制原始版本中的任何内容时都必须构建一个新的vector,这意味着您在向量中存储的任何类型的n个结构。

b)您不必编写显式循环并手动调用push_back。这使得函数g()变得不必要。

c)最后,随后,您将避免对g()的函数调用。使用返回值res初始化g()通常不是问题,因为返回值优化无需复制vector中的本地g()(这正是调用g++初始化g()res的作用。但是,这更多的是代码减少而不是性能优化 - 您可以减少编写和维护的功能。

答案 4 :(得分:0)

根据qwm对该问题的评论,使用

e = f(i,std::move(e));

会为您保存一份副本,而另外两份预期副本实际上会移动。

  • 通过副本传递参数
  • 通过副本返回(实际上是移动构造函数)
  • 分配回您的对象(实际上是移动作业)

还有一个从g返回的举动。

但正如其他答案所述,请使用pass-by-reference。或者直接使用向量而不是结构(向量知道它们自己的长度)。

顺便说一句,如果你事先知道你的矢量将包含多少元素,只需设置矢量的大小并使用循环来分配元素,就像你将数组一样。由于每次调用时的容量检查,使用push_back会产生开销。