【问题标题】:How does RVO and rvalue returned by the function work?函数返回的 RVO 和 rvalue 是如何工作的?
【发布时间】:2016-06-14 22:05:42
【问题描述】:

为了了解编译器如何选择类的构造函数,我编写了以下代码:

#include <iostream>

struct Widget
{
    Widget(Widget&& w){std::cout << "Move ctor" << std::endl;}
    Widget(void){std::cout << "Default ctor" << std::endl;}
    Widget(const Widget& w){std::cout << "Copy ctor" << std::endl;}
};

Widget make_widget(void) //helper function
{
    Widget w;
    return w;
}

int main(void)
{
    Widget w(make_widget());
}

根据 Effective Modern C++ 的第 25 项,由于返回值优化,编译器将 w 视为右值引用。所以我期望Widget w(make_widget()) 调用移动构造函数。但事实并非如此。此外,它只打印

Default

所以我不知道调用了哪个版本的构造函数。 然后我也尝试显式返回右值。即return std::move(w) . 考虑到上述结果,与我的预期相反,它正确调用了移动构造函数,并打印了

Default
Move

看来我正处于右值的迷宫中。请告诉我那里发生了什么。

【问题讨论】:

  • 你忘记的一个重要部分,它与RVO 密切相关,是copy elision,它将解释你的行为。
  • 您似乎对什么是 RVO 感到困惑。 RVO 删除对移动构造函数的不必要调用。
  • 如果您不将所有内容都称为w...,您的文字会更容易阅读
  • 我好像误解了 RVO 的工作。我现在逐渐了解 RVO 和优化。

标签: c++ c++11 move-semantics rvalue-reference rvo


【解决方案1】:

根据 Effective Modern C++ 的第 25 项,由于返回值优化,编译器将 w 视为右值引用。

不,我确定作者不是那个意思。 RVO 与是否为右值引用无关。 make_widgetmain 中的 wWidget 类型的左值。如果你有一个Widget&amp;&amp; 类型的变量,它的值类别是左值(因为它有一个标识),它的类型是右值引用。

您看到的Default 是由于make_widget() 内部的Widget w;。由于返回值优化,避免了将其复制到main 中的w 的操作,因此您看不到更多内容。如果您想在没有 RVO 的情况下查看输出,则将 -fno-elide-constructors 传递给 g++(如果您使用它(使用 VC++ 禁用 RVO isn't possible),您会看到

Default (first creation)
Move    (creation of temporary with the move ctor)
Move    (copying of temporary to the one in main)

当您将return w 更改为return std::move(w) 时,为返回而创建的临时对象是使用右值构造的,因此您会在输出中看到一个额外的Move

【讨论】:

  • 上下文“使用移动ctor创建临时”中的“临时”是否意味着make_widget的返回值?
  • @BenjaminLindley 感谢您注意到这一点;令人困惑,是的。现已修复!
  • @darkspider temporary 的类型与函数的返回类型相同,但是它的值类别是右值,因为它没有名称。不允许使用包含对它产生副作用的右值的表达式。它can only be bound 到一个右值引用或一个 const 左值引用或复制到一个左值。
  • 我终于明白了,多亏了(N)RVO,这段代码的处理就好像make_widget中的w是直接在main中的w中构造的一样。这样,在禁用 RVO 时出现的两个移动被消除,并且只打印“默认”。考虑到这种行为,为了保持其可移植性,不提倡编写具有任何副作用的构造函数,因为结果可能会根据优化是打开还是关闭而改变。我希望我不会误解你的全部解释。
  • @darkspider:构造函数可以产生副作用的一种情况是,当这些副作用被析构函数撤消时。例如std::shared_ptr,复制构造函数的副作用是增加存储对象的引用计数,而析构函数减少它。
猜你喜欢
  • 2012-03-28
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 2011-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多