【问题标题】:Returning by value vs rvalue reference按值返回与右值引用
【发布时间】:2018-12-15 16:29:40
【问题描述】:

question about returning temporaries 回复后,我注意到第二个回复略有不同。

它不是按值返回,而是按右值引用返回。 您能解释一下这些方法之间的区别以及它们面临的风险吗?

struct Bar
    {
    Bar& doThings() & {return *this;}

     // Returning rvalue reference
    Bar&& doThings() &&    {return std::move(*this);}

     // Alternative: Returning by value
    //Bar doThings() && {return std::move(*this);}

    std::unique_ptr<int> m_content; // A non-copyable type
    };

【问题讨论】:

  • 那是因为std::unique_ptr 没有实现复制行为。它只能移动。
  • 对于这种情况没关系,因为在被调用者中返回的值无论如何都是右值。
  • 如果调用者在不使用返回值的情况下执行doThings();那么第三个版本将破坏被处理的对象,而前两个不会

标签: c++ reference rvalue return-by-reference return-by-value


【解决方案1】:

我对这个很感兴趣,所以我敲了一个小测试程序:

#include <memory>
#include <iostream>

struct Bar
{
    // Return by reference
    Bar& doThings() & { return *this; }

    // Return by rvalue reference
    Bar&& doThings() && { std::cout << "in Bar&& doThings, this=" << (void *) this << "\n"; return std::move (*this); }

    ~Bar () { std::cout << "Bar destroyed at " << (void *) this << "\n"; }

    int first;
    std::unique_ptr<int> m_content; // Make Bar a non-copyable type
};

int main ()
{
    std::cout << std::hex;
    Bar bar;
    std::cout << "bar is at " << &bar << "\n";
    Bar& bar_ref = bar.doThings ();
    std::cout << "bar_ref refers to " << &bar_ref.first << "\n\n";
    Bar&& bar_rvalue_ref = Bar ().doThings ();
    std::cout << "bar_rvalue_ref refers to " << &bar_rvalue_ref.first << "\n\n";
}

输出:

bar is at 0x7ffdc10846f0
bar_ref refers to 0x7ffdc10846f0

in Bar&& doThings, this=0x7ffdc1084700
Bar destroyed at 0x7ffdc1084700
bar_rvalue_ref refers to 0x7ffdc1084700

Bar destroyed at 0x7ffdc10846f0

所以这告诉我们doThings() 的任何一种形式都不会创建临时文件。您也可以通过Godbolt 验证这一点。

那么,看看Bar 的实现,这对我们有什么影响?好吧,我个人完全看不出Bar&amp;&amp; doThings() 有什么用处,因为:

  1. AFAICT,您只能在新构造的对象上调用它,所以无论如何它似乎没什么意义。
  2. 因为它从*this移动,原则上它会吃掉新构造的对象,所以返回的引用(仍然引用它)是不可依赖的。
  3. 正如@xskxzr 已经指出的那样,在步骤 1 中创建的临时对象的生命周期非常短。

Bar&amp; doThings() 工作得很好(并且实际上返回了一个指向对象本身的指针,因此不会涉及复制或临时操作)。

我确定我错过了一些重要的事情,所以我很想听听人们对此的反应。通过右值引用返回肯定有一些的目的,也许在更复杂的场景中。谢谢。

Live demo

【讨论】:

    【解决方案2】:

    一个主要区别是,如果返回右值引用,当返回值绑定到引用时,临时的生命周期不会延长。

    例如,

    Bar&& rb = Bar{}.doThings();
    use(rb); // not safe if returning rvalue reference, while safe if returning by value
    

    【讨论】:

    • 那会是std::move 临时创建的吗? (只是为了澄清我自己的想法)
    • std::move 本身不会创建临时的。如果按值返回,则从std::move(*this) 创建并初始化一个新的临时(作为返回值)。在我的示例中延长了新临时对象的生命周期。
    • @xskxzr 当do_things() 返回一个右值引用时,为什么Bar&amp;&amp; rb = ... 没有延长生命周期?我浏览了en.cppreference.com/w/cpp/language/reference_initialization,在“临时文件的生命周期”下有 4 个要点和最后一个声明:“一般来说,临时文件的生命周期不能通过“传递”进一步延长:第二个引用,从临时绑定的引用,不会影响其生命周期。“这适用于这里吗?还是第 3 点?
    • @phön 对,这正是“第二个参考”案例。
    • @xskxzr std::move(*this) 返回的引用被认为是第一个,doThings 函数返回的引用被认为是第二个,而最终的 Bar&amp;&amp; rb 被认为是第三个?从技术上讲,是否可以应用复制省略和 RVO 并让 Bar&amp;&amp; rb 成为此 sn-p 中的第一个也是唯一一个引用?从技术的角度来看,在这个例子中似乎可以延长生命周期,它只是违反“当前规则”,还是?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-22
    相关资源
    最近更新 更多