何时返回函数外部的值使用move vs copy?

时间:2014-05-28 14:21:24

标签: c++ c++11 copy return move

阅读此question后。我创建了这个小小的测试:

class A{
public:
    A(){}
    A(const A&){printf("copy\n");}
    A(A&&){printf("move\n");}

    static A f(){
        A a;
        return a;}

    static A g(){
        A a;
        return (a);}//could be return *&a; too.

    static A h(){
        A a;
        return true?a:a;}

 };

结果是(没有RVO和NRVO):

  • f使用移动
  • g使用move
  • h使用副本

据我所知,12.8.32中描述了用于决定是否使用复制或移动的规则:

  • 满足复制操作的省略标准或将满足时,除了源对象是函数参数,并且要复制的对象由左值指定,首先执行重载决策以选择副本的构造函数,就好像该对象是由rvalue指定的一样。 ...

其中涉及12.8.31的规则:(我只显示相关部分)

  • 在具有类返回类型的函数的return语句中,当表达式为非易失性自动对象的名称(除函数或catch子句参数外)具有相同的cvunqualified类型作为函数返回类型,通过将自动对象直接构造到函数的返回值中可以省略复制/移动操作
  • 当复制/移动尚未绑定到引用(12.2)的临时类对象时 对于具有相同cv-unqualified类型的类对象,可以省略复制/移动操作 将临时对象直接构造到省略的copy / move
  • 的目标中

按照这些规则,我理解f和h会发生什么:

  • f中的副本有资格获得elision,因此会被移动。 (参见粗体部分)
  • h中的副本不符合elision的条件,因此会被复制。

g怎么样?

对我来说,它看起来很像h。我正在返回一个表达式,它不是自动对象的名称,因此我认为它会被复制但是它被移动了。这里发生了什么?

2 个答案:

答案 0 :(得分:11)

在大多数情况下,写a(a)没有区别。规范的相关部分是§5.1p6(强调我的):

  

带括号的表达式是一个主表达式,其类型和值与所附表达式的类型和值相同。括号的存在不会影响表达式是否为左值。除非另有说明,否则带括号的表达式可以与可以使用封闭表达式的上下文完全相同,并且含义相同

因此,相同的推理适用于您为g提供的函数f的返回值。


在upcomming标准C ++ 14中,这已被阐明§12.8p32(强调我的):

  

当满足复制/移动操作的省略标准时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式为a(可能带括号)id-expression命名一个对象,该对象具有在body或statement-declaration-clause中最内层封闭函数或lambda-expression声明的自动存储持续时间,用于选择复制的构造函数的重载决策是第一个如同对象由右值指定一样。


对于那些想知道的人,当括号重要时,这是一个例子:

namespace N {
struct S { };

  void f(S);

}

void g() {
  N::S s;
  f(s); // OK: calls N::f
  (f)(s); // error: N::f not considered; parentheses
          // prevent argument-dependent lookup
}

答案 1 :(得分:0)

请注意,如果您声明

const A a;

在您的示例中,它们都会复制。 该标准的第12.8节说“过载分辨率 首先执行选择复制的构造函数,就像对象是由rvalue指定的那样,“但如果a是const,那将是一个const rvalue,它与移动构造函数不匹配。