繁体   English   中英

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

[英]how to use c++11 move semantics to explicitly avoid copying

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

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

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

如何显式确保e中的数据没有被多次复制。

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

与将e定义为普通的非恒定引用相比,使用move有何不同?

#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使用非常量引用,并在不牺牲效率的情况下获得非副作用代码的外观/效果。 我认为这是移动语义为我们带来的好处,对吗? (允许将标准容器“复制”到周围)。 我伪造len字段和struct来说明情况,因为stl容器已经具有所有move语义处理程序。

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

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

如何显式确保e中的数据没有被多次复制。

通过引用传递arr。

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

在这种情况下,这不是一个好的选择。 您将结果分配回e(这样就可以了)。 问题是,如果您在f中执行某些操作会导致失败(引发异常)。 在这种情况下,不仅会丢失所有更改,而且还会丢失所有e (因为它的值将被移至f而不分配回去)。

与将e定义为普通的非恒定引用相比,使用move有何不同?

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

如果我理解问题,可以使用指针或引用

如何显式确保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 ),则可能要为它们保留空间,这样可以更有效。

作为对utnapistim的回答的补充,我为您提供了可以实现相同结果的替代方法,即使用C ++ 11语言和库功能用n个连续的整数值填充向量。

考虑到您显然希望将一个vector和一个似乎要存储数组大小的int分组的事实,我可以想出的最短版本是

#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()完全是没有必要的,但是最糟糕的部分是每次您要push_back一个整数都复制arr 这意味着您每次都必须构造一个新vector 复制原始vector任何内容,这意味着您要在向量中存储任何类型的n个构造。

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

c)最后,随后避免使用函数g() 使用g()的返回值初始化res通常不是问题,因为返回值优化消除了在g()复制本地vector的需要(这正是g++在调用g()初始化res时所做的工作) 。 但是,与性能优化相比,这更多的是代码减少-您要编写和维护的功能要少得多。

每个qwm对问题的评论,使用

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

会为您节省一份,而实际上还有两份预期的副本正在移动。

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

从g返回还有一个动作。

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

顺便说一句,如果您事先知道向量将包含多少个元素,则只需设置向量的大小并使用循环就可以像分配数组一样分配元素。 由于每个调用都要进行容量检查,因此使用push_back会产生开销。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM