【问题标题】:Move Semantics C++11 (Bjarne Stroustrup book, pg75)移动语义 C++11(Bjarne Stroustrup 书,pg75)
【发布时间】:2014-03-18 03:50:16
【问题描述】:

我试图让我清楚移动语义。我正在关注 Bjarne Stroustrup 第 4 版的示例,我真的迷路了。

他说当很多元素(在类向量中)时,对象的副本可能会很昂贵,因此移动语义是解决方案。

想一想:

向量结果 = vector1 + vector2 + vector3;

顺序可能是错误的,但它会(vector2 + vector3)生成部分结果result1,result1 + vector1,生成结果;

我重载了operator+:

Vector operator+(const Vector &a,const Vector &b)
{

  if(a.size()!=b.size()) {
      throw length_error{"Length of vectors must match for summing"};
  }

  Vector result(a.size());
  cout << "Intermediate result for sum created" << endl;
  for(long unsigned  int i=0; i<result.size();i++){
      result[i] = a[i] + b[i];
  }
  return result;
}

我还创建了一个移动构造函数:

Vector::Vector(Vector&& orig) noexcept
  :elem{orig.elem},
   sz{orig.sz}
{
  cout << "Move constructor called" << endl;
  orig.elem = nullptr;          // We own the array now
  orig.sz = 0;
}

因此不执行复制操作。但是有些事情我不明白。一个只与 c++ 相关,另一个与 c++11 相关。

首先:

如你所见,当 operator+ 退出时,Vector 结果应该被销毁。但不是在内容被复制到另一个向量实例之后。我称之为部分结果1。

这永远不会发生,程序的输出是这样的:

  • 创建了新的类向量
  • 已创建总和的中间结果
  • 已创建新类向量 转到结果
  • 已创建总和的中间结果
  • 类向量被破坏

但是直到程序结束我才看到第二个部分结果被破坏。我也没有看到任何复制操作(我已经完成了 operator= 和复制构造函数)。

所以我不明白。如果变量 Vector 结果的范围在 operator+ 内,则必须将内容复制到另一个变量。那还没有完成。似乎总和的最后一个结果只是分配给总和的向量结果。

第二:

不执行任何移动操作。我无法让移动语义在这里工作。我查看了http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52745 并应用了那里的内容,但无论如何都没有移动操作。

每个结果都是正确的。

很明显,我做错了什么。

您可以从这里获取整个代码(使用 autotools config + eclipse):

http://static.level2crm.com/cx11-learn-20140218.tar.bz2

谁能解释一下这里发生了什么?

最好的问候,

【问题讨论】:

  • 比搬家更便宜的是(N)RVO,这就是你所看到的。
  • 如果您使用的是 g++,请尝试设置 -fno-elide-constructors。 (正如 jrok 所说,您看到的应该是 NRVO 的结果)

标签: c++ c++11 vector


【解决方案1】:

首先,这里没有移动构造。发生了 NRVO(根据您的问题中的 cmets),但如果您想证明移动构造发生,您应该尝试复制一个临时的。尝试按如下方式重新实现您的运算符:

// Note that the first parameter is taken by value.
Vector operator+(Vector a, const Vector &b)
{
  cout << "Intermediate result for sum created or moved from a" << endl;

  // NOTE: Could be implemented as operator+= and written a += b.
  if(a.size()!=b.size()) {
      throw length_error{"Length of vectors must match for summing"};
  }

  for(long unsigned  int i=0; i<a.size();i++){
      a[i] += b[i];
  }
  // END NOTE

  return a;
}

请注意,您的操作员现在需要一个本地“副本”作为第一个参数。在第一个总和中,由于将提供 Vector&amp; 作为参数(对象具有名称),因此将调用复制构造函数并创建一个具有新缓冲区的对象(您对 result 局部变量所做的事情) .然后,将形成一个临时对象,因此在第二次调用 operator+ 时,将提供 Vector&amp;&amp; 作为第一个参数,并调用移动构造函数,从而有效地窃取临时对象的缓冲区。

如果您按照 cmets 防止构造函数省略,您应该能够看到调用了一些移动构造函数。

编辑:更详细的解释为什么你不会在你的情况下采取行动。简短的版本是 C++ 没有魔法:)

在链式添加的情况下移动的目的是避免由于创建临时对象而重复创建 Vectors 的缓冲区。目的不是因为临时对象的销毁而丢失缓冲区,而是重用临时对象的缓冲区来存储结果。

在您的情况下,您甚至没有尝试复制 Vector 的缓冲区。你在这一行明确地创建了一个新的缓冲区:

Vector result(a.size());

您是在说“请为结果创建一个大小合适的向量”,并且每次添加都执行一次。如果你知道a 很快就会死掉,你最好说“请窃取a 的缓冲区”。是这样写的:

Vector result(a);

但是要让它工作,a 必须引用一个很快就会死掉的对象,这意味着a 必须是Vector&amp;&amp; 类型。完成后,result 已经包含了a 的值,因此您所要做的就是添加b 的元素的值。

for(long unsigned  int i=0; i<result.size();i++){
  result[i] += b[i];
}

(用+= 表达+ 是惯用的) 所以为了证明移动构造,你应该有一个operator+的定义,其签名是:

Vector operator+(Vector&& a, const Vector &b)  {
  if(a.size()!=b.size()) {
    throw length_error{"Length of vectors must match for summing"};
  }

  Vector result(a);
  cout << "Intermediate result for sum moved from a" << endl;
  for(long unsigned  int i=0; i<result.size();i++){
    result[i] += b[i];
  }
  return result;
}

但这不适用于第一次添加,因为它不是将临时添加到另一个命名对象,而是添加两个命名对象。你应该有一个处理左值引用的 operator+ 重载:

Vector operator+(const Vector& a, const Vector &b) {
  if(a.size()!=b.size()) {
    throw length_error{"Length of vectors must match for summing"};
  }

  Vector result(a); // In this case, this is a copy
  cout << "Intermediate result for sum created" << endl;
  for(long unsigned  int i=0; i<result.size();i++){
    result[i] += b[i];
  }
  return result;
}

请注意,两个版本共享相同的文本。这带来了另一种惯用的做法:让编译器通过要求operator+ 的左侧按值传递来决定是移动还是复制,只需要一个重载。 (这是我回答开头的版本。

【讨论】:

  • 嗨 Laurent,我不知道 NRVO,但这是有道理的。我一直认为 c++ 效率低下,因为我不知道像 NRVO 这样的优化。关于这个问题,我期望元素总和之间有一个移动运算符/构造函数。在某种程度上,部分结果将被移动到 operator+ 而不是复制它。因为在参数 a 上复制后,它将被丢弃,因为不再需要。为什么不这样做?
  • 因为通过您编写operator+ 的方式,您明确地形成了一个左值引用。 (const Vector&amp; a) 你的operator+ 不能主动窃取参数的资源,因为它无法知道调用方,它指的是临时的。如果您希望能够安全地窃取资源,您的第一个参数应该只绑定到右值引用 (Vector&amp;&amp; a) 并且不是const。 (因为引用的对象必须不属于它的缓冲区,所以修改)如果你的a参数是&amp;&amp;,你可以用result(a) thru movector构造你的result向量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-23
  • 2014-08-16
相关资源
最近更新 更多