C++ C++;为什么返回右值引用会改变调用者';函数签名不返回右值引用时的行为?
我遇到了一些关于右值返回的行为,我无法理解。 假设我们有以下结构:C++ C++;为什么返回右值引用会改变调用者';函数签名不返回右值引用时的行为?,c++,move-semantics,rvalue,copy-elision,C++,Move Semantics,Rvalue,Copy Elision,我遇到了一些关于右值返回的行为,我无法理解。 假设我们有以下结构: struct Bar { int a; Bar() : a(1) { std::cout << "Default Constructed" << std::endl; } Bar(const Bar& Other) : a(Other.a) { std::cout << &quo
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);
}
};
现在我明白了为什么Bar MoveConstructedExplicit(myFoo.GetBarRValueExplicit())
调用移动构造函数。
但是由于函数Foo::GetBarRValue()
没有显式返回一个条&
,我希望它的调用给出与Foo::GetBar()
相同的行为。我不明白在这种情况下为什么/如何调用move构造函数。据我所知,没有办法知道GetBarRValue()
的实现将myBar
转换为右值引用
我的编译器是不是在耍优化的把戏(在VisualStudio的调试构建中测试这个,显然返回值优化不能被禁用)?
我发现有点令人不安的是,调用方的行为会受到GetBarRValue()
实现的影响。GetBarRValue()
签名中没有任何内容告诉我们,如果调用两次,它将给出未定义的行为。因此,在我看来,当函数没有显式返回&&&时,返回std::move(x)是一种不好的做法
有人能给我解释一下这里发生了什么事吗?谢谢 发生的事情是,你在那里看到了elision。您正在使用一个简单的
条类型对返回std::move(x)
进行移动构造;然后编译器将删除该副本
您可以看到GetBarRValue
的非优化程序集。对move构造函数的调用实际上发生在GetBarRValue
函数中,而不是在返回时。回到main
,它只是在做一个简单的lea
,它根本没有调用任何构造函数。发生的事情是,您在那里看到了省略。您正在使用一个简单的条类型对返回std::move(x)
进行移动构造;然后编译器将删除该副本
您可以看到GetBarRValue
的非优化程序集。对move构造函数的调用实际上发生在GetBarRValue
函数中,而不是在返回时。回到main
,它只是在做一个简单的lea
,根本不调用任何构造函数。关键是
Bar myBar;
是Foo
的数据成员。因此,对于每个Foo
的成员函数,其生存时间都比它们的长。换句话说,这些函数中的每一个都返回一个值或对其范围大于函数范围的值的引用
现在,
编译器可以“看到”您返回的值在函数完成后仍然有效。函数必须“按值”返回其值,并且由于其参数肯定不是临时的,编译器将选择复制构造函数
如果您这样尝试此功能:
Bar GetBar()
{
Bar myBar; // shadows this->myBar
return myBar;
}
编译器应该注意到返回值的作用域即将过期,因此它会将其“种类”从l值更改为r值,并使用移动构造函数(或复制省略,但情况不同)
第二个功能:
Bar GetBarRValue()
{
return std::move(myBar);
}
在这里,编译器可以“看到”与以前相同的返回值:该值必须“按值”传递。但是,程序员已经将myBar的“种类”从l值更改为x值(可寻址但可作为临时对象处理的对象)。这意味着:“嘿,编译器,myBar
的状态不再需要保护,你可以窃取它的内容。”。编译器将顺从地选择move构造函数。因为你,程序员,让“他”这样做
在第三种情况下
Bar&& GetBarRValueExplicit()
{
return std::move(myBar);
}
编译器不会进行任何转换,也不会调用任何构造函数。只返回一个“r值引用”类型的引用(“伪装的指针”)。然后,该值将用于初始化一个对象,MoveConstructed
,这是根据其参数类型调用move构造函数的地方。关键是
Bar myBar;
是Foo
的数据成员。因此,对于每个Foo
的成员函数,其生存时间都比它们的长。换句话说,这些函数中的每一个都返回一个值或对其范围大于函数范围的值的引用
现在,
编译器可以“看到”您返回的值在函数完成后仍然有效。函数必须“按值”返回其值,并且由于其参数肯定不是临时的,编译器将选择复制构造函数
如果您这样尝试此功能:
Bar GetBar()
{
Bar myBar; // shadows this->myBar
return myBar;
}
编译器应该注意到返回值的作用域即将过期,因此它会将其“种类”从l值更改为r值,并使用移动构造函数(或复制省略,但情况不同)
第二个功能:
Bar GetBarRValue()
{
return std::move(myBar);
}
在这里,编译器可以“看到”与以前相同的返回值:该值必须“按值”传递。但是,程序员已经将myBar的“种类”从l值更改为x值(可寻址但可作为临时对象处理的对象)。这意味着:“嘿,编译器,myBar
的状态不再需要保护,你可以窃取它的内容。”。编译器将顺从地选择move构造函数。因为你,程序员,让“他”这样做
在第三种情况下
Bar&& GetBarRValueExplicit()
{
return std::move(myBar);
}
编译器不会进行任何转换,也不会调用任何构造函数。只返回一个“r值引用”类型的引用(“伪装的指针”)。然后,该值将用于初始化对象,MoveConstructed
,这是调用move构造函数的地方,基于其参数的类型。FWIW,如果编译器可以看到定义(即,它在同一源文件中,或它在头文件中),则编译器可以知道它执行