首页 > 解决方案 > C ++为什么当函数签名不返回右值引用时返回右值引用会改变调用者的行为?

问题描述

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

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()没有显式返回 aBar&&我希望它的调用给出与Foo::GetBar(). 我不明白在这种情况下为什么/如何调用移动构造函数。据我所知,没有办法知道强制转换为 rValue 引用的GetBarRValue()实现myBar

我的编译器是否在对我进行优化(在 Visual Studio 的调试版本中对此进行测试,显然无法禁用返回值优化)?我觉得有点令人沮丧的是,调用方的行为可能会受到GetBarRValue(). 签名中没有任何GetBarRValue()内容告诉我们,如果调用两次,它将给出未定义的行为。return std::move(x)在我看来,正因为如此,当函数没有显式返回 && 时,这是一种不好的做法。

有人可以向我解释这里发生了什么吗?谢谢!

标签: c++move-semanticsrvaluecopy-elision

解决方案


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

GetBarRValue 您可以在此处查看未优化的程序集。对移动构造函数的调用实际上是在GetBarRValue函数中发生的,而不是在返回时发生的。回到main,它只是做一个简单的lea,它根本没有调用任何构造函数。


推荐阅读