c++ 的 const reference extend lifetime rvalue

c++ 中,如果一个函数返回一个对象,那么这个对象是如果返回的呢?

本文用 OSX 下的 llvm 来汇编理解这个定义。

// life_expansion.cpp
#include <iostream>
#include <functional>
#include <boost/type_index.hpp>
using namespace std;
class Foo {
  public:
    Foo(int v):i(v) {
        cout <<  __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
             << "this "  << this << " "
             << "i "  << i << " "
             << endl;
    }
    Foo(const Foo& foo):i(foo.i + 1) {
        cout <<  __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
             << "i "  << i << " "
             << endl;
    }
    ~Foo() {
        cout <<  __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
             << "i "  << i << " "
             << "this "  << this << " "
             << endl;
        i = -100;
    }
  public:
    int i = 0;
};

Foo calc()
{
    Foo foo(1);
    return foo;
}
void show(const Foo& f){
    printf("%d\nq", f.i);
}
int main(int argc, char *argv[])
{
    printf("before\n");
    {
        const auto& f1 = calc();
        show(f1);
    }
    printf("after\n");
    return 0;
}

我们看一下,产生的汇编

% c++ -O3 -fno-inline -std=c++11 -o a.out life_expansion.cpp
otool -tV a.out | c++filt

截取其中的汇编代码

_main:
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   %rbx
        pushq   %rax
        leaq    0x869(%rip), %rdi ## literal pool for: "before"
        callq   0x100000d10 ## symbol stub for: _puts # 这里调用 printf
        leaq    -0x10(%rbp), %rbx                     # 这里申请临时变量的内存
        movq    %rbx, %rdi
        callq   calc() ## calc()
        movq    %rbx, %rdi
        callq   show(Foo const&) ## show(Foo const&)
        movq    %rbx, %rdi
        callq   Foo::~Foo() ## Foo::~Foo()
        leaq    0x848(%rip), %rdi ## literal pool for: "after"
        callq   0x100000d10 ## symbol stub for: _puts
        xorl    %eax, %eax
        addq    $0x8, %rsp
        popq    %rbx
        popq    %rbp
        retq
        nopw    %cs:(%rax,%rax)
calc():
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   %rbx
        pushq   %rax
        movq    %rdi, %rbx
        movl    $0x1, %esi
        callq   Foo::Foo(int) ## Foo::Foo(int)
        movq    %rbx, %rax
        addq    $0x8, %rsp
        popq    %rbx
        popq    %rbp
        retq
        nopl    (%rax)

这里可以看到,对于 calc 的返回值,实际分配内存是在调用者的堆栈里申请的。

而如果调用者,用一个 const reference 抓住(引用) 这个变量的话,那么这个临时变量的生命周期会被拉长。

他的生命周期拉长到和 const reference 的生命周期一样。

详见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1993/N0345.pdf