从rvalue对象返回成员

时间:2018-01-28 19:38:13

标签: c++ move-semantics rvalue

让我们采取两个结构/类

struct C1{
  C1(){};
  C1(C1&){std::cout<<"copy"<<std::endl;}
  C1(C1&&){std::cout<<"move"<<std::endl;}};

struct C2{
  C1 c;
  C2(){};
  C1 get1(){return c;}
  C1 get2(){return std::move(c);}};

而不是

C1 a1=C2().c;
C1 a2=C2().get1();
C1 a3=C2().get2();

输出

move
copy
move

问题

我们知道rvalues的成员本身就是rvalues。这就是为什么使用a1,调用移动构造函数。为什么,在a2的情况下调用复制构造函数。我们从函数返回一个右值。

换句话说,std :: move强制转换为右值。但是,作为一个rvalue的成员,c,已经是一个rvalue。为什么a2和a3的行为之间存在差异?

2 个答案:

答案 0 :(得分:7)

好问题。沉闷的答案是,C ++规范中没有任何规则表明从这样的垂死对象中返回一个成员会自动移动它。

您可能会对 rvalue对此的引用感兴趣。它允许您在&&&上重载,以便您可以手动实现预期的行为:

struct C2{
  C1 c;
  C2(){};
  C1 get1() & { std::cout << "&" << std::endl; return c;}
  C1 get1() && { std::cout << "&&" << std::endl; return std::move(c);}
  C1 get2(){return std::move(c);}
};

现在

C1 a2=C2().get1();

打印

&&
move

很酷,但非常罕见。

相关:

答案 1 :(得分:2)

  

我们知道rvalues的成员本身就是rvalues。

是的,这是真的,因为州[expr.ref]/4.2(强调我的):

  

如果E2是非静态数据成员且E1的类型是“cq1 vq1 X”,并且E2的类型是“cq2 vq2 T”,则表达式指定由第一个表达式指定的对象的指定成员。 如果E1是左值,则E1.E2是左值;否则E1.E2是一个xvalue。让符号vq12代表vq1和vq2的“联合”;也就是说,如果vq1或vq2是易失性的,则vq12是易失性的。同样,让符号cq12代表cq1和cq2的“联合”;也就是说,如果cq1或cq2是const,则cq12是const。如果E2被声明为可变成员,那么E1.E2的类型是“vq12 T”。如果E2未被声明为可变成员,则E1.E2的类型为“cq12 vq12 T”。

而且,来自[expr.ref]/4.5

  

如果E2是成员枚举器且E2的类型是T,则表达式E1.E2是prvalue。 E1.E2的类型是T.

到目前为止一切顺利。如果E1本身是左值,则只能获得左值,否则它是x值或prvalue。

  

但是,作为右值的成员,c,已经是一个右值。

这是你的假设错误的地方。

来自[class.this]/1(强调我的)

  

在非静态(9.3)成员函数的主体中,关键字this是一个值为prvalue的表达式   是调用该函数的对象的地址。 在成员函数中的类型   X类是X *

thisX*类型的prvalue,取消引用类型为X的指针会产生类型为X的左值。

由于访问成员函数内的成员等同于(*this).m,因此m通过类型为X的左值进行访问。

所以你的代码相当于:

C1 get1() { return (*this).c; }
//      lvalue ----^       ^--- must be an lvalue too then.

由于this始终是相同的类型,因此即使使用函数ref-qualifier,成员函数内的表达式c也将始终为左值:

C1 get1() && { return (*this).c; }
//                            ^---- lvalue again, accessing through a pointer