【问题标题】:Returning a member from an rvalue object从右值对象返回成员
【发布时间】:2018-01-28 19:38:12
【问题描述】:

让我们采用两个结构/类

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

问题

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

换句话说,std::move 强制转换为右值。但是,作为右值的成员,c 已经是一个右值。为什么 a2 和 a3 的行为有区别?

【问题讨论】:

  • 你的复制构造函数根本不能用右值调用。每当你看到它被调用时,它并不是复制一个右值,而是出于其他原因。

标签: c++ move-semantics rvalue


【解决方案1】:

好问题。枯燥的答案是,C++ 规范中没有任何规则规定从这样的垂死对象中返回成员会自动移动它。

您可能对所谓的 对 this 的右值引用感兴趣。它可以让你在 &amp;&amp;&amp; 上重载,这样你就可以手动实现你期望的行为:

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

很酷,但很少见。

相关:

【讨论】:

  • 或许应该有这样的规则。是时候编写标准提案了吗?
  • 感谢您的回答,让我放心了。我以为我错过了一些重要的东西,但正如你所说,这只是一个缺失的规则。我知道过载的可能性,我只是试图提供最小的行为示例;由副本返回的右值(右值的成员)。谢谢你:)
【解决方案2】:

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

是的,这是真的,正如 [expr.ref]/4.2 所述(强调我的):

如果 E2 是一个非静态数据成员并且 E1 的类型是“cq1 vq1 X”,并且 E2 的类型是“cq2 vq2 T”,则表达式指定第一个表达式指定的对象的命名成员. 如果 E1 是左值,则 E1.E2 是左值;否则 E1.E2 是一个 xvalue。 让符号 vq12 代表 vq1 和 vq2 的“并集”;也就是说,如果 vq1 或 vq2 是 volatile,则 vq12 是 volatile。类似地,让符号 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 是纯右值。 E1.E2的类型是T。

到目前为止一切顺利。只有当E1 本身是一个左值时,你才能获得一个左值,否则它是一个 xvalue 或 prvalue。

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

这就是你的假设是错误的。

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

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

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

由于访问成员函数内部的成员等效于(*this).m,因此通过X 类型的左值访问m

所以你的代码相当于:

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

由于this 始终是同一类型,因此即使使用函数引用限定符,成员函数内的表达式c 也始终是左值:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-25
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多