如何解决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()
没有明确返回 Bar&&
,我希望它的调用与 Foo::GetBar()
的行为相同。我不明白为什么/如何在这种情况下调用移动构造函数。据我所知,没有办法知道 GetBarRValue()
的实现将 myBar
强制转换为 rValue 引用。
我的编译器是否在对我玩优化技巧(在 Visual Studio 的调试版本中对此进行测试,显然无法禁用返回值优化)?
我觉得有点令人沮丧的是,调用方的行为可能会受到 GetBarRValue()
的实现的影响。 GetBarRValue()
签名中没有任何内容告诉我们,如果调用两次,它将给出未定义的行为。在我看来,当函数没有显式返回 && 时,return std::move(x)
是不好的做法。
有人可以向我解释这里发生了什么吗?谢谢!
解决方法
发生的事情是你在那里看到了省略。您正在使用简单类型的 return std::move(x)
在 Bar
上移动构造;然后编译器正在删除副本。
您可以看到未优化的 GetBarRValue
here 程序集。对移动构造函数的调用实际上发生在 GetBarRValue
函数中,而不是在返回时。回到 main
,它只是做了一个简单的 lea
,根本没有调用任何构造函数。
重点是
Bar myBar;
是 Foo
的数据成员。因此,对于Foo
的每一个成员函数来说,它的生存时间都比他们的要长。换句话说,这些函数中的每一个都返回一个值或对范围大于函数范围的值的引用。
现在
Bar GetBar()
{
return myBar;
}
编译器可以“看到”您返回的值将在函数完成后有效。函数必须“按值”返回它的值,而且由于它的参数肯定不是临时的,编译器会选择复制构造函数。
如果你像这样试验这个函数:
Bar GetBar()
{
Bar myBar; // shadows this->myBar
return myBar;
}
编译器应该注意到返回值的范围即将到期,因此它将其“种类”从左值更改为右值并使用移动构造函数(或复制省略,但这是另一回事)。
第二个函数:
Bar GetBarRValue()
{
return std::move(myBar);
}
这里编译器可以“看到”与以前相同的返回值:该值必须“按值”传递。但是,程序员已将 myBar
的“种类”从左值更改为 x 值(可寻址的对象,但可以视为临时对象)。这意味着:“嘿,编译器,myBar
的状态不再需要保护,你可以窃取它的内容”。编译器会乖乖地选择移动构造函数。因为你,程序员,让“他”这样做。
在第三种情况下,
Bar&& GetBarRValueExplicit()
{
return std::move(myBar);
}
编译器不会做任何转换,不会调用构造函数。只会返回类型为“r 值引用”的引用(“伪装的指针”)。然后,这个值将用于初始化一个对象,MoveConstructed
,这是根据其参数的类型调用移动构造函数的地方。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。