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

时间:2016-08-12 03:08:26

标签: c++ reference const

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

std::vector< Thing > getThings();

void do() {
    const std::vector< Thing > &things = getThings();   
}

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

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

我的假设是编译器实现了RVO,或者它没有实现RVO,并且说左值应该是const&不会暗示或强制无副本的情况。

修改

我特别询问const&这里的使用是否会减少副本,而不是临时对象的生命周期,如"the most important const"

中所述

进一步澄清问题

这是:

const std::vector< Thing > &things = getThings();

与此不同:

std::vector< Thing > things = getThings();

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

2 个答案:

答案 0 :(得分:2)

从语义上讲,编译器需要一个可访问的拷贝构造函数,在调用站点,即使稍后,编译器也忽略了对拷贝构造函数的调用 - 优化在编译后期完成在语义分析阶段之后阶段。

阅读完评论后,我想我更了解你的问题。现在让我详细回答一下。

想象一下,该函数有这个return语句:

return items;

从语义上讲,编译器需要一个可访问的拷贝构造函数(或move-constructor),这可以省略。但是,仅仅为了争论,假设它在这里复制并且副本存储在__temp_items中,我将其表达为:

__temp_items <= return items; //first copy: 

现在在呼叫站点,假设您没有使用const &,所以它变为:

std::vector<Thing> things = __temp_items;  //second copy

现在你可以看到自己,有两个副本。允许编译器忽略它们。

但是,您的实际代码使用const &,因此它变为:

const std::vector<Thing> & things = __temp_items;  //no copy anymore.

现在,语义上只有一个副本,编译器仍然可以省略它。至于第二个副本,我不会说const& &#34;阻止&#34; 它在编译器优化它的意义上,而不是语言允许开始

但有趣的是,无论编译器在返回时复制多少次,或者删除少数(或全部)复制,返回值 临时。如果是这样,那么如何绑定临时工作?如果这也是你的问题(现在我知道这不是你的问题但是保持这种方式,以便我不必删除我的答案的这部分),然后是的它有效并且保证用语言。

正如文章the most imporant const中非常详细地解释的那样,如果const引用绑定到临时,那么临时的生命周期将延长到引用范围和它与对象的类型无关

在C ++ 11中,还有另一种延长临时生命周期的方法,即rvalue-reference:

std::vector<Thing> && things = getThings();    

它具有相同的效果,但优点(或缺点 - 取决于上下文)是您也可以修改内容。

我个人更喜欢把它写成:

auto && things = getThings();   

但是那不是必然一个rvalue-reference - 如果你改变了函数的返回类型,返回一个引用,那么things就会绑定到lvalue-reference 。如果你想讨论这个问题,那就完全不同了。

答案 1 :(得分:1)

嘿所以你的问题是:

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

忽略临时的生命周期,因为这不是你问的问题,我们可以通过查看程序集输出来了解发生的事情。我正在使用clang,llvm 7.0.2。

这是标准的盒子。按价值回报,没什么特别的。

测试A

class MyClass
{
public:
    MyClass();
    MyClass(const MyClass & source);
    long int m_tmp;
};

MyClass createMyClass();

int main()
{
    const MyClass myClass = createMyClass();
    return 0;
}

如果我使用“-O0 -S -fno-elide-constructors”编译,我就会得到这个。

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

我们只关注调用代码。我们对createMyClass的实现不感兴趣。那是在其他地方编译的。 因此createMyClass在临时内创建类,然后将其复制到myClass中。

Simples。

const ref版本怎么样?

测试B

class MyClass
{
public:
    MyClass();
    MyClass(const MyClass & source);
    long int m_tmp;
};

MyClass createMyClass();

int main()
{
    const MyClass & myClass = createMyClass();
    return 0;
}

相同的编译器选项。

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

没有复制构造函数,因此更正确吗?

如果我们关闭两个版本的“-fno-elide-constructors”会怎样?仍然保持-O0。

测试A

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

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

测试B

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

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

如果我们将优化设置为-O1

,该怎么办?
_main:
    pushq   %rbp                    # Boiler plate
    movq    %rsp, %rbp              # Boiler plate
    subq    $16, %rsp               # Reserve 16 bytes for the stack frame
    leaq    -8(%rbp), %rdi          # arg0 = &___temp_items = rdi = rbp-8
    callq   __Z13createMyClassv     # createMyClass(arg0)
    xorl    %eax, %eax              # ex = 0, return value for main
    addq    $16, %rsp               # Pop stack frame
    popq    %rbp                    # Boiler plate
    retq

使用-O1编译时,两个源文件都会变为此文件。 它们导致完全相同的汇编程序。 对于-O4也是如此。

编译器不知道createMyClass的内容,所以它无法做更多的优化。

使用我正在使用的编译器,从分配给const ref没有性能提升。

我认为g ++和intel的情况类似,但检查总是好的。