C++ 为什么当函数签名不 return 右值引用时 returning 右值引用会改变调用者的行为?

C++ Why does returning rvalue reference change caller's behavior when function signature does not return rvalue reference?

我遇到了一些关于右值 return 我无法理解的行为。 假设我们有以下结构:

struct Bar
{
   int a;

   Bar()
      : a(1)
   {
      std::cout << "Default Constructed" << std::endl;
   }

   Bar(const Bar& Other)
      : a(Other.a)
   {
      std::cout << "Copy Constructed" << std::endl;
   }

   Bar(Bar&& Other)
      : a(Other.a)
   {
      std::cout << "Move Constructed" << std::endl;
   }

   ~Bar()
   {
      std::cout << "Destructed" << std::endl;
   }

   Bar& operator=(const Bar& Other)
   {
      a = Other.a;
      std::cout << "Copy Assigment" << std::endl;
      return *this;
   }

   Bar& operator=(Bar&& Other) noexcept
   {
      a = Other.a;
      std::cout << "Move Assigment" << std::endl;
      return *this;
   }
};

struct Foo
{
   Bar myBar;

   Bar GetBar()
   {
      return myBar;
   }

   // Note that we are not returning Bar&&
   Bar GetBarRValue()
   {
      return std::move(myBar);
   }

   Bar&& GetBarRValueExplicit()
   {
      return std::move(myBar);
   }
};

使用如下:

int main()
{
   Foo myFoo;

   // Output:
   // Copy Constructed
   Bar CopyConstructed(myFoo.GetBar());

   // Output:
   // Move Constructed
   Bar MoveConstructedExplicit(myFoo.GetBarRValueExplicit());

   // Output:
   // Move Constructed
   //
   // I don't get it, GetBarRValue() has has the same return type as GetBar() in the function signature.
   // How can the caller know in one case the returned value is safe to move but not in the other?
   Bar MoveConstructed(myFoo.GetBarRValue());
}

现在我明白为什么 Bar MoveConstructedExplicit(myFoo.GetBarRValueExplicit()) 调用移动构造函数了。 但是由于函数 Foo::GetBarRValue() 没有明确地 returns a Bar&& 我希望它的调用给出与 Foo::GetBar() 相同的行为。我不明白 why/how 在这种情况下会调用移动构造函数。据我所知,没有办法知道 GetBarRValue() 的实现将 myBar 转换为 rValue 引用。

我的编译器是否在对我玩优化技巧(在 Visual Studio 中的调试版本中对此进行测试,显然 return 值优化无法禁用)? 令我感到有些苦恼的是,调用方的行为可能会受到 GetBarRValue() 的影响。 GetBarRValue() 签名中没有任何内容告诉我们如果调用两次它将给出未定义的行为。因此在我看来,当函数没有明确地 returns a &&.

时,return std::move(x) 是不好的做法

谁能给我解释一下这是怎么回事?谢谢!

发生的事情是你在那里看到了省略。您正在使用 Bar 的简单类型在 return std::move(x) 上移动构造;然后编译器正在删除副本。

可以看到GetBarRValuehere的非优化汇编。对移动构造函数的调用实际上是在 GetBarRValue 函数中发生的,而不是在返回时发生的。回到 main,它只是做了一个简单的 lea,它根本没有调用任何构造函数。

重点是

   Bar myBar;

Foo 的数据成员。所以,对于Foo的每一个成员函数来说,它的生存时间都比它们长。换句话说,这些函数中的每一个 return 都是一个值或对范围大于函数范围的值的引用。

现在,

Bar GetBar()
   {
      return myBar;
   }

编译器可以“看到”您 return 函数完成后将存在的值。该函数必须 return 它的值“按值”,并且由于它的参数肯定不是临时的,编译器将选择复制构造函数。

如果您像这样试验过此功能:

Bar GetBar()
   {
      Bar myBar; // shadows this->myBar
      return myBar;
   }

编译器应该注意到 return 值的范围即将到期,因此它会将其“种类”从左值更改为右值并使用移动构造函数(或复制省略,但是这是一个不同的故事)。

第二个函数:

   Bar GetBarRValue()
   {
      return std::move(myBar);
   }
 

编译器在这里可以“看到”与以前相同的 return 值:该值必须“按值”传递。但是,程序员已将 myBar 的“种类”从左值更改为 x 值(可寻址的对象,但可以将其视为临时对象)。这意味着:“嘿,编译器,myBar 的状态不再需要保护,你可以窃取它的内容”。编译器会乖乖的选择移动构造函数。因为你这个程序员,让“他”这么做了。

第三种情况,

   Bar&& GetBarRValueExplicit()
   {
      return std::move(myBar);
   }

编译器不会进行任何转换,也不会调用构造函数。只是一种“r 值引用”的引用(“伪装的指针”)将被 returned。然后,这个值将用于初始化一个对象,MoveConstructed,这是根据其参数类型调用移动构造函数的地方。