复制ctor调用而不是移动ctor

时间:2017-08-22 02:09:53

标签: c++ c++11 constructor copy-constructor move-constructor

为什么从 int totalRows= sheet.Rows.Length; CellRange to; for (int CurrentRow = 1; CurrentRow < totalRows+1; CurrentRow++) { string formula = string.Format("=SUM(F" + "{0}" + ",G" + "{0}" + ")", CurrentRow); to = sheet.Range[CurrentRow,cl+1]; to.Formula = formula; } 而不是移动构造函数返回时调用了复制构造函数?

bar

如果#include <iostream> using namespace std; class Alpha { public: Alpha() { cout << "ctor" << endl; } Alpha(Alpha &) { cout << "copy ctor" << endl; } Alpha(Alpha &&) { cout << "move ctor" << endl; } Alpha &operator=(Alpha &) { cout << "copy asgn op" << endl; } Alpha &operator=(Alpha &&) { cout << "move asgn op" << endl; } }; Alpha foo(Alpha a) { return a; // Move ctor is called (expected). } Alpha bar(Alpha &&a) { return a; // Copy ctor is called (unexpected). } int main() { Alpha a, b; a = foo(a); a = foo(Alpha()); a = bar(Alpha()); b = a; return 0; } 执行bar,则行为符合预期。我不明白为什么调用return move(a)是必要的,因为std::move在返回时调用了移动构造函数。

4 个答案:

答案 0 :(得分:4)

在这种情况下,有两件事需要理解:

    a中的
  1. bar(Alpha &&a)是一个命名的右值参考;因此,被视为左值。
  2. a仍是参考。
  3. 第1部分

    由于a中的bar(Alpha &&a)是一个命名的右值引用,因此将其视为左值。将命名的右值引用视为左值的动机是提供安全性。请考虑以下内容,

    Alpha bar(Alpha &&a) {
      baz(a);
      qux(a);
      return a;
    }
    

    如果baz(a)a视为右值,则可以自由调用移动构造函数,qux(a)可能无效。该标准通过将命名的右值引用视为左值来避免此问题。

    第2部分

    由于a仍然是引用(并且可能引用bar范围之外的对象),bar在返回时会调用复制构造函数。这种行为背后的动机是提供安全。

    <强>参考

    1. SO Q&A - return by rvalue reference
    2. Kerrek SB的评论

答案 1 :(得分:1)

是的,非常困惑。我想引用另一篇SO帖子implicite move。在哪里我发现以下评论有点令人信服,

  

因此,标准委员会决定你必须这样做   明确关于任何命名变量的移动,无论其如何   参考类型

实际上“&amp;&amp;”已经指示放手了,当你做“返回”时,移动就足够安全了。

可能只是标准委员会的选择。

scott meyers的“有效的现代c ++”第25项,也对此进行了总结,没有给出太多解释。

Alpha foo() {
  Alpha a
  return a; // RVO by decent compiler
}
Alpha foo(Alpha a) {
  return a; // implicit std::move by compiler
}

Alpha bar(Alpha &&a) {
  return a; // Copy ctor due to lvalue
}

Alpha bar(Alpha &&a) {
  return std:move(a); // has to be explicit by developer
}

答案 2 :(得分:1)

当人们首次了解右值参考时,这是一个非常常见的错误。基本问题是类型值类别之间的混淆。

int是一种类型。 int&是另一种类型。 int&&是另一种类型。这些都是不同的类型。

左值和右值是称为值类别的东西。请在这里查看精彩的图表:What are rvalues, lvalues, xvalues, glvalues, and prvalues?。你可以看到除了左值和右值之外,我们还有prvalues和glvalues和xvalues,它们形成了各种维恩图的关系。

C ++有一些规则可以说各种类型的变量可以绑定到表达式。然而,表达式引用类型被丢弃(人们经常说表达式没有引用类型)。相反,表达式有一个值类别,它确定哪些变量可以绑定到它。

换句话说:右值引用和左值引用仅与赋值的左侧直接相关,即创建/绑定变量。在右侧,我们讨论的是表达式而不是变量,而rvalue / lvalue reference-ness仅与确定值类别的上下文相关。

一个非常简单的例子是简单地查看纯粹类型int的事物。作为表达式的int类型的变量是左值。但是,由评估返回int的函数组成的表达式是rvalue。这对大多数人来说都是直观的;关键是要分离表达式的类型(甚至在引用被丢弃之前)和它的值类别。

这导致的是,即使int&&类型的变量只能绑定到rvalues,也不意味着所有类型为int&&的表达式, rvalues。事实上,正如http://en.cppreference.com/w/cpp/language/value_category中的规则所述,任何由命名变量组成的表达式总是左值,无论类型

这就是为什么你需要std::move来将rvalue引用传递给rvalue引用的后续函数。这是因为右值引用绑定到其他右值引用。它们绑定到右值。如果你想获得移动构造函数,你需要给它一个rvalue来绑定,并且一个命名的右值引用不是右值。

std::move是一个返回右值引用的函数。这种表达的价值范畴是什么?一个右值?不。它是一个xvalue。这基本上是一个右值,有一些额外的属性。

答案 3 :(得分:1)

foobar中,表达式a是左值。语句return a;表示从初始化程序a初始化返回值对象,并返回该对象。

两种情况之间的区别在于,根据a声明为最内层封闭块内的非易失性自动对象或函数参数,执行此初始化的重载分辨率会有所不同。

适用于foo而非bar。 (在bar中,a被声明为参考)。因此return a;中的foo选择移动构造函数来初始化返回值,但return a;中的bar选择复制构造函数。

全文是C ++ 14 [class.copy] / 32:

  

当满足复制/移动操作的省略条件时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式为(可能是括号内的)时)id-expression命名一个对象,该对象具有在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的自动存储持续时间,首先执行重载决策以选择复制的构造函数,就好像指定了对象一样通过右值。如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值。 [注意:无论是否发生复制省略,都必须执行此两阶段重载决策。如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数。 - 后注]

其中&#34;符合复制/移动操作的标准&#34;是指[class.copy] /31.1:

  
      
  • 在具有类返回类型的函数的return语句中,当表达式是非易失性自动对象的名称(函数或catch子句参数除外),其具有与函数相同的cv-unqualified类型返回类型,可以通过构造省略复制/移动操作   自动对象直接进入函数的返回值
  •   

请注意,这些文本将针对C ++ 17进行更改。