通过右值引用返回是否更有效?

Is returning by rvalue reference more efficient?

提问人:Neil G 提问时间:7/13/2009 最后编辑:AustinWBryanNeil G 更新时间:11/18/2023 访问量:99692

问:

例如:

Beta_ab&& Beta::toAB() const {
    return move(Beta_ab(1, 1));
}
C++ C++11 右值引用

评论

1赞 Maxim Komlik 12/12/2023
对于那些寻找为什么在返回时创建的临时语句的生存期没有延长的答案(因为右值 (T&&) 引用和常量左值 (const T&) 引用通常会延长临时语句的生存期),请参阅相关 cppreference 页面上临时部分的生存期中的排除段落

答:

308赞 Johannes Schaub - litb 7/13/2009 #1
Beta_ab&&
Beta::toAB() const {
    return move(Beta_ab(1, 1));
}

这将返回一个悬空引用,就像左值引用案例一样。函数返回后,临时对象将被销毁。应按值返回,如下所示Beta_ab

Beta_ab
Beta::toAB() const {
    return Beta_ab(1, 1);
}

现在,它正确地将临时对象移动到函数的返回值中。如果编译器可以,它将通过使用 RVO(返回值优化)完全避免移动。现在,您可以执行以下操作Beta_ab

Beta_ab ab = others.toAB();

它会将临时构造移动到 中,或者执行 RVO 以完全省略移动或复制。我建议您阅读 BoostCon09 Rvalue References 101,它解释了这个问题,以及 (N)RVO 如何与此相互作用。ab


在其他情况下,返回右值引用的情况将是一个好主意。想象一下,你有一个函数,你经常在临时函数上调用它。让它返回右值临时值的常量左值引用并不是最佳选择。你可以像这样实现它getAB()

struct Beta {
  Beta_ab ab;
  Beta_ab const& getAB() const& { return ab; }
  Beta_ab && getAB() && { return move(ab); }
};

请注意,在这种情况下不是可选的,因为既不是本地自动值,也不是临时值。现在,ref-qualifier 表示在 rvalue 临时函数上调用第二个函数,进行以下移动,而不是复制moveab&&

Beta_ab ab = Beta().getAB();

评论

72赞 deft_code 7/15/2009
我一直认为,当返回类型是 r 值引用时,悬空引用问题会自动消失。很高兴我在它咬我之前就把它弄清楚了。堆栈粉碎虫子很烂。
34赞 Johannes Schaub - litb 3/10/2010
:)实际上,右值引用是“只是引用”,就像左值引用一样。他们不复制或存储任何东西。
11赞 galinette 4/11/2014
成员上的常量和限定符比简单的常量更能发挥作用吗?
4赞 Siu Ching Pong -Asuka Kenji- 3/30/2015
+1-ed,但断开的链接:BoostCon09 右值参考文献 101
3赞 Malcolm 10/20/2015
@galinette 这些是 ref-qualifiers
-2赞 wonder.mice 4/10/2018 #2

例如,在稍微不同的上下文中,它可以更有效:

template <typename T>
T&& min_(T&& a, T &&b) {
    return std::move(a < b? a: b);
}

int main() {
   const std::string s = min_(std::string("A"), std::string("B"));
   fprintf(stderr, "min: %s\n", s.c_str());
   return 0;
}

有趣的是,在我的机器上,为上述代码生成了 54 条指令,而为常规代码生成了 62 条指令。但是,它为上述代码生成 518 条指令,而常规代码生成 481 条指令。clang++ -O3std::min-O0std::min

更新

对于不同意或不理解这个答案的人,我建议先在编译器资源管理器(又名 godbolt (https://godbolt.org))中玩这个示例,然后再说有问题:

// use compiler flags: -O3 --std=c++20 -fno-exceptions
#include <utility>
#include <string>

// Example of the `min(a, b)` implementation that can take advantage of return by rvalue.
template <typename T>
T&& min_(T&& a, T &&b) {
    return std::move(a < b? a: b);
}

// Test class that supports both copy and move construction.
struct Bar {
    Bar();
    Bar(const Bar &other);
    Bar(Bar &&other);
    ~Bar();
    char *ptr;
    friend bool operator <(const Bar& lhs, const Bar& rhs);
};

// Just to tell the optimizer that we care about the value.
void consume(const void *);

#if 1
void with_rvalue() {
   const Bar s = min_(Bar(), Bar());
   consume(&s);
}
#else
void without_rvalue() {
   const Bar s = std::min(Bar(), Bar());
   consume(&s);
}
#endif

我们得到:with_rvalue()

        Bar::Bar() [object constructor]
        Bar::Bar() [object constructor]
        operator<(Bar const&, Bar const&)
        Bar::Bar(Bar&&) [move object constructor]
        Bar::~Bar() [object destructor]
        Bar::~Bar() [object destructor]
        consume(void const*)
        Bar::~Bar() [object destructor]

我们得到:without_rvalue()

        Bar::Bar() [object constructor]
        Bar::Bar() [object constructor]
        operator<(Bar const&, Bar const&)
        Bar::Bar(Bar const&) [copy object constructor]
        Bar::~Bar() [object destructor]
        Bar::~Bar() [object destructor]
        consume(void const*)
        Bar::~Bar() [object destructor]

正如你所看到的,当按 rvalue 返回时,我们避免了对象复制。这个原语的实用性是另一个主题,但这里有一个示例,说明按右值返回如何生成更优化的代码。

评论

0赞 Deqing 4/11/2018
我对你的回答感到困惑。尝试过类似的(也许)版本但失败了: ideone.com/4GyUbZ 你能解释一下为什么吗?
0赞 wonder.mice 4/12/2018
您在临时对象中使用了引用,并集成了过度释放的对象。修复:ideone.com/tQVOalfor(:)
20赞 xdavidliu 11/4/2019
这个答案真的不是错的吗?对于模板参数 T,T&& 不是 r 值引用,而是通用引用,在这种情况下,我们应该始终调用 std::forward<T>,而不是 std::move!更不用说,这个答案直接与上面得票最高的答案相矛盾。
2赞 wonder.mice 4/16/2020
@xdavidliu这个答案是一个人为的例子,如何通过右值返回可以更有效。 只是用作明确的演员表,以更清楚地说明这一点。它不是要复制粘贴到项目中的代码。它与得票最高的答案并不矛盾,因为在函数内部创建了临时对象。这里返回的对象是参数之一(临时对象作为评估(词法上)包含创建它们的点的完整表达式的最后一步被销毁)。std::move()
9赞 xdavidliu 4/16/2020
@wonder.mice 那么请将 T 替换为 std::string;这里根本没有必要使用模板,使用 T&& 作为 r 值参考只是糟糕的风格,不必要地混淆了模板和 r 值的新手。