ZhiBing's blog(码上看世界) ZhiBing's blog(码上看世界)
首页
  • Linux工具链

    • shell命令
  • 构建

    • CMake
    • Makefile
  • 版本管理

    • Git
    • Github
  • IDE及工具

    • vscode
    • CLion
  • 设计模式

    • 设计原则
  • 编程语言

    • C++
    • Go
    • Python
    • Shell
  • 调试

    • gdb
  • 开发者测试

    • gtest
  • 系统支撑

    • 操作系统
  • 性能优化

    • 编译优化选项
    • perf
    • valgrind
  • 容器

    • Docker
  • 微服务

    • Rancher
  • 其他
  • 随笔
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

ZhiBing Zheng

时间会回答成长
首页
  • Linux工具链

    • shell命令
  • 构建

    • CMake
    • Makefile
  • 版本管理

    • Git
    • Github
  • IDE及工具

    • vscode
    • CLion
  • 设计模式

    • 设计原则
  • 编程语言

    • C++
    • Go
    • Python
    • Shell
  • 调试

    • gdb
  • 开发者测试

    • gtest
  • 系统支撑

    • 操作系统
  • 性能优化

    • 编译优化选项
    • perf
    • valgrind
  • 容器

    • Docker
  • 微服务

    • Rancher
  • 其他
  • 随笔
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • 编程语言

    • C++

      • shared_ptr(智能指针)
      • unique_ptr
      • bind函数
      • cout
      • C++ main函数的传入参数(argc, argv)
      • GLog使用
      • signals2
      • 编译期分派
      • 成员函数的调用效率
      • 返回值优化
        • C++ 返回值优化(RVO)
        • 测试一:直接返回临时对象
        • 测试二:运行时决定返回临时对象
        • 结论
      • 函数调用栈
      • 空类大小
      • 指针和数组的区别
      • 遵守三五原则
      • const限定符
      • static限定符
      • virtual限定符
      • 常用的CXX_FLAGS
      • 模版之AnyType
      • 模版之编译期断言
      • 模版之变参模板
    • Go

    • Python

    • shell

    • Rust

  • 调试

  • 开发者测试

  • 系统支撑

  • 性能优化

  • 通用领域
  • 编程语言
  • C++
zhengzhibing
2022-06-16
目录

返回值优化

# C++ 返回值优化(RVO)

对于 C++中函数返回临时对象,通常观点是会产生临时对象,有额外开销,这是真的吗?

首先构造一个类用于测试,测试环境信息:Linux, GCC 9.3.0

class RvoClass {
public:
    explicit RvoClass(int a) : a(a) { cout << "constructor" << endl; }

    RvoClass(const RvoClass& rvo)
    {
        cout << "copy constructor" << endl;
        a = rvo.a;
    }

    RvoClass(RvoClass&& rvo) noexcept
    {
        cout << "move constructor" << endl;
        a = rvo.a;
    }

    RvoClass& operator=(const RvoClass& rvo)
    {
        cout << "copy assignment" << endl;
        a = rvo.a;
        return *this;
    }

    RvoClass& operator=(RvoClass&& rvo) noexcept
    {
        cout << "move assignment" << endl;
        a = rvo.a;
        return *this;
    }

    ~RvoClass() { cout << "destructor" << endl; }

private:
    int a;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 测试一:直接返回临时对象

RvoClass func2()
{
    RvoClass rvo(10);
    return rvo;
}

int main(int argc, char** argv)
{
    {
        RvoClass r2 = func2();
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

测试结果如下:

constructor
destructor
1
2

出乎意料之外,并没有额外开销,那其中到底发生了什么?让我们看看汇编。

objdump -M intel -S -C -d rvo
1
RvoClass func2()
{
    1270:       f3 0f 1e fa             endbr64
    1274:       55                      push   rbp
    1275:       48 89 e5                mov    rbp,rsp
    1278:       48 83 ec 20             sub    rsp,0x20
    127c:       48 89 7d e8             mov    QWORD PTR [rbp-0x18],rdi
    1280:       64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
    1287:       00 00
    1289:       48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
    128d:       31 c0                   xor    eax,eax
    RvoClass rvo(10);
    128f:       48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
    1293:       be 0a 00 00 00          mov    esi,0xa
    1298:       48 89 c7                mov    rdi,rax
    129b:       e8 fe 00 00 00          call   139e <RvoClass::RvoClass(int)>
    return rvo;
    12a0:       90                      nop
}
    12a1:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
    12a5:       64 48 33 04 25 28 00    xor    rax,QWORD PTR fs:0x28
    12ac:       00 00
    12ae:       74 05                   je     12b5 <func2()+0x45>
    12b0:       e8 0b fe ff ff          call   10c0 <__stack_chk_fail@plt>
    12b5:       48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
    12b9:       c9                      leave
    12ba:       c3                      ret



00000000000012e2 <main>:

int main(int argc, char** argv)
{
    12e2:       f3 0f 1e fa             endbr64
    12e6:       55                      push   rbp
    12e7:       48 89 e5                mov    rbp,rsp
    12ea:       48 83 ec 20             sub    rsp,0x20
    12ee:       89 7d ec                mov    DWORD PTR [rbp-0x14],edi
    12f1:       48 89 75 e0             mov    QWORD PTR [rbp-0x20],rsi
    12f5:       64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
    12fc:       00 00
    12fe:       48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
    1302:       31 c0                   xor    eax,eax
    {
        RvoClass r2 = func2();
    1304:       48 8d 45 f4             lea    rax,[rbp-0xc]
    1308:       48 89 c7                mov    rdi,rax
    130b:       e8 60 ff ff ff          call   1270 <func2()>
    1310:       48 8d 45 f4             lea    rax,[rbp-0xc]
    1314:       48 89 c7                mov    rdi,rax
    1317:       e8 16 01 00 00          call   1432 <RvoClass::~RvoClass()>
    }
    return 0;
    131c:       b8 00 00 00 00          mov    eax,0x0
}
    1321:       48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
    1325:       64 48 33 14 25 28 00    xor    rdx,QWORD PTR fs:0x28
    132c:       00 00
    132e:       74 05                   je     1335 <main+0x53>
    1330:       e8 8b fd ff ff          call   10c0 <__stack_chk_fail@plt>
    1335:       c9                      leave
    1336:       c3                      ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

通过汇编可以看出在func2函数中创建了临时对象,然后将这个临时对象的地址返回。

这是因为编译器做了返回值优化RVO,从 C++11 开始支持此特性。

下面直接返回临时对象,也只执行一次构造。

RvoClass func3()
{
    return RvoClass(8);
}
1
2
3
4

# 测试二:运行时决定返回临时对象

RvoClass func1(int a)
{
    if (a > 10) {
        RvoClass rvo(a - 10);
        return rvo;
    } else {
        RvoClass rvo(a);
        return rvo;
    }
}

int main(int argc, char** argv)
{
    {
        RvoClass r1 = func1(11);
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

测试结果如下:

constructor
move constructor
destructor
destructor
1
2
3
4

从测试结果看,编译器并没有做RVO优化。但由于我们定义了移动构造函数,编译器做了拷贝优化,如果注释掉移动构造函数,你会发现结果是调用了拷贝构造函数。使用移动构造函数可以减少调用拷贝构造函数所带来的开销。

# 结论

首先如果明确要求避免拷贝构造,那应该将函数外定义,通过引用传参(或指针传参)的方式来避免额外的拷贝操作。

如下方式:

void func(RvoClass& rvo){    ...}RvoClass r(0);func(r);
1

对于运行期才能决定如何返回临时变量的函数,不会有RVO,但可能有拷贝优化。

对于编译期就能知道如何返回临时变量的函数,会有RVO(其实需要看编译器是否支持)。

对于函数返回值的类型做了转换的,不会有RVO。

对于函数返回右值引用,不会有RVO,但可能有拷贝优化;

#C++
上次更新: 2022/06/17, 07:22:19
成员函数的调用效率
函数调用栈

← 成员函数的调用效率 函数调用栈→

最近更新
01
HPE gen10 plus 安装ESXI 7
06-12
02
ESXI 7安装黑群晖
06-12
03
ESXI 7安装win10
06-12
更多文章>
Theme by Vdoing | Copyright © 2022-2024 ZhBing Zheng | 粤ICP备2022062743号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式