26 June 2022

C++ 核心指南目录

F.45: Don’t return a T&&

理由

返回T&&意味着返回已经销毁的临时对象。&& 就好比是吸附临时对象的磁铁。

例子

当你返回一个右值引用时,它的作用域范围在返回语句前已经结束了!

auto&& x = max(0, 1);   // OK, so far
foo(x);                 // Undefined behavior

这种用法经常导致 bug,编译器也会报错。函数开发人员应该避免这种陷阱。

安全生命周期开发要求及时发现这种问题。

例子

临时返回的右值引用,可以再次转交给调用的函数。但是不能向上返回给上一层函数内使用。对于引用或完美传递(perfect forwarding)方式传入的参数,又要返回值的,可以用 auto 推演返回类型(注意不是auto&&

假设 F 通过值返回:

// -*- compile-command: "g++ -std=c++20 code.cpp && ./a"; -*-
#include <iostream>
#include <gsl/gsl>
using namespace std;
using namespace gsl;
template<class F>
auto&& wrapper(F f)
{
    cout << typeid(f).name(); // or whatever instrumentation
    return f();               // BAD: returns a reference to a temporary
}
int func() {
    return 100;
}

int main()
{
    wrapper(func);  // no way to get return value
    return 0;
}

以下写法更好一些:

// -*- compile-command: "g++ -std=c++20 code.cpp && ./a"; -*-
#include <iostream>
#include <gsl/gsl>
using namespace std;
using namespace gsl;
template<class F>
auto wrapper(F f)
{
    cout << typeid(f).name() << endl; // or whatever instrumentation
    return f();          // OK
}
int func() {
    return 100;
}

int main()
{
    cout << wrapper(func);
    return 0;
}
PFivE
100

例外

std::movestd::forward确实返回&&,不过只是一次类型转换,用于在临时对象还没有被销毁前,将其引用传递出去。

强化

  • 除了std::movestd::forward 之外,返回&&的都要标记警告。