【问题标题】:Why Structured Bindings disable both RVO and move on return statement?为什么结构化绑定禁用 RVO 并继续返回语句?
【发布时间】:2018-09-10 18:46:00
【问题描述】:

假设我们有一个名为AAA 的类同时支持复制/移动

class AAA
{
public:
    AAA() = default;
    ~AAA() = default;

    AAA(const AAA& rhs)
    {
       std::cout << "Copy constructor" << std::endl;
    }

    AAA(AAA&& rhs)
    {
       std::cout << "Move constructor" << std::endl;
    }
};

在以下代码中,get_val 返回second

AAA get_val()
{
    auto [ first, second ]  = std::make_tuple(AAA{}, AAA{});

    std::cout << "Returning - " << std::endl;
    return second;
}

auto obj = get_val();
std::cout << "Returned - " << std::endl;

现在second 被复制,打印以下输出:

...
Returning - 
Copy constructor 
Returned -

这很不幸,因为我对结果的期望是要么没有调用复制构造函数,要么至少它被隐式移动了。

为避免复制,我必须在其上明确应用std::move

return std::move(second);

然后我会收到以下结果:

...
Returning - 
Move constructor 
Returned - 

我认为未执行 RVO 的原因可能是编译器会将second 视为参考,而get_val 返回prvalue。

但是为什么也不能期望隐式移动呢? 在这种特殊情况下,在 return 语句上使用显式 std::move 看起来并不直观,因为 you generally don't want to make RVO, which is in most cases a better optimization than move, accidentally gone away

由编译器 gcc 和 clang 用-O3 测试。

Live Demo

【问题讨论】:

  • NRVO 显然是不可能的:这需要在调用上下文中构造对象,但实际构造是 tuple,而不是 AAA。名称first, second 指的是元组的元素。目前,“移动上下文”是 NRVO 的一个子集,但我看不出为什么不能为非参考结构化绑定的成员添加移动上下文。但是手动编写 std::move(second) 是安全的,因为没有 NRVO,所以这不可能是“悲观移动”的情况
  • @MM 对。我得到了 rvo 无法执行的部分,并且手动应用 std::move 使其安全。但为什么呢?让编译器为我们确保这一点会很好。我想知道标准中是否有任何引用阻止编译器应用隐式std::move
  • 标准规定这种“隐式移动”仅适用于“命名具有自动存储持续时间的对象的 id 表达式”(参见 [class.copy.elision]/3.1)。这不适用于结构化绑定,这些名称将结果命名为 std::get 。我将您的问题解释为询问其背后的基本原理
  • @M.M 我不明白为什么您的报价会阻止省略。 [dcl.struct.bind]/3 中的最后一句非常混乱。看来这里second本身应该是一个副本,因此满足“自动存储持续时间”。
  • @liliscent in auto [b] = e;,绑定绑定到 e 的副本。您似乎混淆了,e 与标准提到的e 不同(绑定绑定到的隐藏对象)。这与这个问题中的代码无关。 , 其中一个纯右值被绑定到

标签: c++ c++11 c++17 rvo structured-bindings


【解决方案1】:

但是为什么也不能期望隐式移动呢?

与关闭省略的原因完全相同:因为它是一个引用,而不是独立对象的名称。每次使用second 本质上都等同于说obj.whateverget&lt;1&gt;(obj)(尽管在后一种情况下,我们存储引用)。这些表达式中的任何一个都没有隐含的移动。

结构化绑定用于访问给定对象的子对象。您不能省略子对象的返回,也不能隐式地从它们移开。因此,您不能省略结构化绑定名称,也不能隐式移出它们。

【讨论】:

  • 因为它是一个引用,而不是一个独立对象的名称。 没错,但它在概念上是对隐藏对象的引用。 second的外部状态类型还是AAA,非引用类型,如果你做decltype(second)。如果我们必须不断提醒自己它的幕后总是伴随着std::get,我觉得这违反直觉?
  • @DeanSeo:“没错,但它在概念上是对隐藏对象的引用。” 是的,这就是重点。它是其他对象的一部分;它不独立于隐藏对象。隐式移动别人的零件是不礼貌的。
  • @DeanSeo: "虽然 second 被编译器视为参考" 不,它的处理方式与编译器处理 obj.whatever 的方式完全相同。 decltype(obj.whatever) 将是 AAA,但编译器不会从它移开。
  • @DeanSeo:“它不满足 RVO 和隐式移动的条件,因为它实际上不是(对于编译器)”它不满足这些要求,因为它是不是声明完整对象的变量。它要么是另一个对象 (obj.whatever) 的子对象,要么是对另一个对象 (get&lt;1&gt;(obj)) 的引用,两者都不能被隐式移动。
  • @sandthorn: "一个可以绑定到右值的引用伪装者!?真的吗?" 这个例子中没有任何东西是右值,也没有任何绑定到右值。那你在说什么? “那么这一切会带来什么?” 同样,我不确定你的意思是什么。结构化绑定为您提供了一种访问对象的成员子对象的方法,而无需使用像 get&lt;1&gt; 这样的不透明工具,也不必为值应用有意义的名称。它是纯语法糖。
猜你喜欢
  • 2020-07-09
  • 2011-04-01
  • 1970-01-01
  • 2013-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多