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)
上移动构造;然后编译器正在删除副本。
可以看到GetBarRValue
here的非优化汇编。对移动构造函数的调用实际上是在 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
,这是根据其参数类型调用移动构造函数的地方。
我遇到了一些关于右值 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)
上移动构造;然后编译器正在删除副本。
可以看到GetBarRValue
here的非优化汇编。对移动构造函数的调用实际上是在 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
,这是根据其参数类型调用移动构造函数的地方。