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

问题描述 投票:0回答:2

例如:

Beta_ab&& Beta::toAB() const {
    return move(Beta_ab(1, 1));
}
c++ c++11 rvalue-reference
2个回答
308
投票
Beta_ab&&
Beta::toAB() const {
    return move(Beta_ab(1, 1));
}

这会返回一个悬空引用,就像左值引用的情况一样。函数返回后,临时对象将被破坏。您应该按值返回

Beta_ab
,如下所示

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

现在,它正确地将临时

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

Beta_ab ab = others.toAB();

它会将临时构造移动到

ab
中,或者执行RVO以完全省略移动或复制。我建议您阅读BoostCon09 Rvalue References 101,它解释了这个问题,以及 (N)RVO 如何与之交互。


在其他情况下,返回右值引用的情况是一个好主意。想象一下,您有一个经常临时调用的

getAB()
函数。让它返回右值临时值的 const 左值引用并不是最佳选择。你可以这样实现

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

请注意,在这种情况下

move
不是可选的,因为
ab
既不是本地自动右值,也不是临时右值。现在, ref-qualifier
&&
表示在右值临时变量上调用第二个函数,进行以下操作,而不是复制

Beta_ab ab = Beta().getAB();

-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;
}

作为一个有趣的观察,在我的机器上

clang++ -O3
为上面的代码生成 54 条指令,而常规
std::min
则生成 62 条指令。然而,使用
-O0
,它会为上面的代码生成 518 条指令,而常规
std::min
则为 481 条指令。

更新

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

// 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]

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

© www.soinside.com 2019 - 2024. All rights reserved.