const ref是否值得非const func返回值特意减少副本?

jdi*_*jdi 5 c++ const reference

我遇到了一个C++习惯,我试图研究它以了解它的影响并验证它的用法.但我似乎无法找到确切的答案.

std::vector< Thing > getThings();

void do() {
    const std::vector< Thing > &things = getThings();   
}
Run Code Online (Sandbox Code Playgroud)

这里我们有一些返回非const&值的函数.我看到的习惯是在const&从函数中分配返回值时使用左值.提出这种习惯的推理是它减少了副本.

现在我一直在研究RVO(返回值优化),复制省略和C++ 11移动语义.我意识到,无论何处使用,给定的编译器都可以选择通过RVO来防止复制const&.但是,在防止复制方面,const&左值的使用是否会对非const&返回值产生任何影响?移动语义之前,我特别询问了前C++ 11编译器.

我的假设是编译器实现RVO或不实现RVO,并且说左值应该const&不提示或强制无副本情况.

编辑

我特别询问const&这里的用法是否减少了副本,而不是临时对象的生命周期,如"最重要的const"所述

进一步澄清问题

这是:

const std::vector< Thing > &things = getThings();
Run Code Online (Sandbox Code Playgroud)

与此不同的是:

std::vector< Thing > things = getThings();
Run Code Online (Sandbox Code Playgroud)

在减少副本方面?或者它对编译器是否可以减少副本(例如通过RVO)没有任何影响?

Luk*_*uke 1

嘿,所以你的问题是:

\n\n

“当函数按值返回类实例,并将其分配给 const 引用时,是否可以避免复制构造函数调用?”

\n\n

忽略临时的生命周期,因为这不是您要问的问题,我们可以通过查看程序集输出来了解会发生什么。I\xe2\x80\x99m 使用 clang,llvm 7.0.2。

\n\n

这里\xe2\x80\x99s是盒子标准的东西。按价值回报,没什么花哨的。

\n\n

测试A

\n\n
class MyClass\n{\npublic:\n    MyClass();\n    MyClass(const MyClass & source);\n    long int m_tmp;\n};\n\nMyClass createMyClass();\n\nint main()\n{\n    const MyClass myClass = createMyClass();\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我使用 \xe2\x80\x9c-O0 -S -fno-elide-constructors\xe2\x80\x9d 进行编译,我会得到这个。

\n\n
_main:\n    pushq   %rbp                    # Boiler plate\n    movq    %rsp, %rbp              # Boiler plate\n    subq    $32, %rsp               # Reserve 32 bytes for stack frame\n    leaq    -24(%rbp), %rdi         # arg0 = &___temp_items = rdi = rbp-24\n    movl    $0, -4(%rbp)            # rbp-4 = 0, no idea why this happens\n    callq   __Z13createMyClassv     # createMyClass(arg0)\n    leaq    -16(%rbp), %rdi         # arg0 = & myClass\n    leaq    -24(%rbp), %rsi         # arg1 = &__temp_items\n    callq   __ZN7MyClassC1ERKS_     # MyClass::MyClass(arg0, arg1)\n    xorl    %eax, %eax              # eax = 0, the return value for main\n    addq    $32, %rsp               # Pop stack frame\n    popq    %rbp                    # Boiler plate\n    retq\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们只查看调用代码。我们\xe2\x80\x99对createMyClass的实现不感兴趣。\xe2\x80\x99s 在其他地方编译。\n因此 createMyClass 在临时文件中创建该类,然后将其复制到 myClass 中。

\n\n

简单。

\n\n

const ref 版本怎么样?

\n\n

测试B

\n\n
class MyClass\n{\npublic:\n    MyClass();\n    MyClass(const MyClass & source);\n    long int m_tmp;\n};\n\nMyClass createMyClass();\n\nint main()\n{\n    const MyClass & myClass = createMyClass();\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

相同的编译器选项。

\n\n
_main:                              # Boiler plate\n    pushq   %rbp                    # Boiler plate\n    movq    %rsp, %rbp              # Boiler plate\n    subq    $32, %rsp               # Reserve 32 bytes for the stack frame\n    leaq    -24(%rbp), %rdi         # arg0 = &___temp_items = rdi = rbp-24\n    movl    $0, -4(%rbp)            # *(rbp-4) = 0, no idea what this is for\n    callq   __Z13createMyClassv     # createMyClass(arg0)\n    xorl    %eax, %eax              # eax = 0, the return value for main\n    leaq    -24(%rbp), %rdi         # rdi = &___temp_items\n    movq    %rdi, -16(%rbp)         # &myClass = rdi = &___temp_items;\n    addq    $32, %rsp               # Pop stack frame\n    popq    %rbp                    # Boiler plate\n    retq\n
Run Code Online (Sandbox Code Playgroud)\n\n

没有复制构造函数,因此更优化,对吧?

\n\n

如果我们关闭两个版本的 \xe2\x80\x9c-fno-elide-constructors\xe2\x80\x9d 会发生什么?仍然保留-O0。

\n\n

测试A

\n\n
_main:\n    pushq   %rbp                    # Boiler plate\n    movq    %rsp, %rbp              # Boiler plate\n    subq    $16, %rsp               # Reserve 16 bytes for the stack frame\n    leaq    -16(%rbp), %rdi         # arg0 = &myClass = rdi = rbp-16\n    movl    $0, -4(%rbp)            # rbp-4 = 0, no idea what this is\n    callq   __Z13createMyClassv     # createMyClass(arg0)\n    xorl    %eax, %eax              # eax = 0, return value for main\n    addq    $16, %rsp               # Pop stack frame\n    popq    %rbp                    # Boiler plate\n    retq\n
Run Code Online (Sandbox Code Playgroud)\n\n

Clang 删除了复制构造函数调用。

\n\n

测试B

\n\n
_main:                              # Boiler plate\n    pushq   %rbp                    # Boiler plate\n    movq    %rsp, %rbp              # Boiler plate\n    subq    $32, %rsp               # Reserve 32 bytes for the stack frame\n    leaq    -24(%rbp), %rdi         # arg0 = &___temp_items = rdi = rbp-24\n    movl    $0, -4(%rbp)            # rbp-4 = 0, no idea what this is\n    callq   __Z13createMyClassv     # createMyClass(arg0)\n    xorl    %eax, %eax              # eax = 0, return value for main\n    leaq    -24(%rbp), %rdi         # rdi = &__temp_items\n    movq    %rdi, -16(%rbp)         # &myClass = rdi\n    addq    $32, %rsp               # Pop stack frame\n    popq    %rbp                    # Boiler plate\n    retq\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试 B(分配给 const 引用)与之前相同。它现在比测试 A 有更多的指令。

\n\n

如果我们将优化设置为 -O1 会怎样?

\n\n
_main:\n    pushq   %rbp                    # Boiler plate\n    movq    %rsp, %rbp              # Boiler plate\n    subq    $16, %rsp               # Reserve 16 bytes for the stack frame\n    leaq    -8(%rbp), %rdi          # arg0 = &___temp_items = rdi = rbp-8\n    callq   __Z13createMyClassv     # createMyClass(arg0)\n    xorl    %eax, %eax              # ex = 0, return value for main\n    addq    $16, %rsp               # Pop stack frame\n    popq    %rbp                    # Boiler plate\n    retq\n
Run Code Online (Sandbox Code Playgroud)\n\n

当使用 -O1 编译时,两个源文件都会变成这样。\n它们生成完全相同的汇编程序。\n对于 -O4 也是如此。

\n\n

编译器不知道 createMyClass 的内容,因此它无法执行更多优化操作。

\n\n

使用我正在使用的编译器,分配给 const 引用不会带来任何性能提升。

\n\n

我想 g++ 和 intel 的情况类似,尽管检查总是好的。

\n