You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++函数返回大型对象:按const引用还是按值接收更优?

关于值接收与const引用接收返回值的疑问

我一直认为,当接收函数按值返回的大型对象时,使用const引用是正确做法——既能避免临时对象到目标变量的额外拷贝,还能延长临时对象的生命周期,Herb Sutter的文章里也提到过这一点。但我通过汇编对比发现,在-O0优化级别下,按值接收临时返回值的代码反而更短。我了解RVO和NRVO优化,但测试是在-O0下做的,想知道这些优化在-O0下是否还会执行?另外有没有更适合用const引用接收返回值的场景?


测试用C++代码

#include <string>

using namespace std;

string foo() {
    return "test";
}

void bar() {
    auto y = foo();
}

void dar() {
    const auto& z = foo();
}

GCC 14.2生成的x86-64汇编代码

bar()函数汇编

bar():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        lea     rax, [rbp-32]
        mov     rdi, rax
        call    foo[abi:cxx11]()
        lea     rax, [rbp-32]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        leave
        ret

dar()函数汇编

dar():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 48
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    foo[abi:cxx11]()
        lea     rax, [rbp-48]
        mov     QWORD PTR [rbp-8], rax
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        leave
        ret

dar()比bar()多的两条指令:

lea     rax, [rbp-48]
mov     QWORD PTR [rbp-8], rax

问题解答

1. RVO/NRVO在-O0下是否生效?

不会。-O0是GCC的默认无优化级别,编译器会严格遵循C++标准的抽象语义生成代码,不会执行RVO(返回值优化)或NRVO(具名返回值优化)这类改变对象创建位置的优化。这也是两种写法汇编存在差异的核心原因——无优化时编译器会忠实地处理每一步语义。

2. 为什么值接收的汇编更短?

在-O0下:

  • 对于auto y = foo();,C++标准允许拷贝省略:用临时对象初始化同类型对象时,可直接在目标对象的内存位置构造临时对象,无需额外拷贝。因此编译器直接在栈上为y分配空间,调用foo()构造对象,最后析构y,流程更简洁。
  • 对于const auto& z = foo();,编译器需要先创建foo()返回的临时对象,再让引用z指向它。栈上需要额外分配存储临时对象地址的引用变量空间,因此多了两条保存地址的指令,导致汇编代码更长。

3. 适合用const引用接收返回值的场景

虽然直接接收返回值时,值接收通常更优(开启优化后RVO会让两种写法的汇编几乎一致),但以下场景更适合用const引用:

  • 复用临时对象生命周期:如果需要在多个作用域中使用返回的临时对象,const引用可将其生命周期延长至引用的作用域结束,避免多次构造/析构。
  • 函数参数传递:将返回对象传递给其他函数时,用const引用传递可避免拷贝,例如:
    void process(const string& s);
    // 调用时直接传递临时对象的引用,无拷贝
    process(foo());
    
  • 无法触发拷贝省略的场景:当返回值需要转换为其他类型,或编译器无法执行拷贝省略的特殊情况,const引用可避免创建额外的拷贝对象。

注意:非const引用无法绑定到临时对象,只有const引用或右值引用可以。


内容的提问来源于Stack Exchange,提问作者bobeff

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:09:52