Does const ref lvalue match the returned value non-const func specifically shortens the copies?

I came across a C ++ habit that I was trying to explore in order to understand its impact and test its use. But I can not find the exact answer.

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

Here we have a function that returns a value not const& . The habit that I see is using the const& lvalue value when assigning a return value from a function. The suggested argument for this habit is that it reduces the copy.

Now I am looking for RVO (Return Value Optimization), copy elision, and C ++ 11 move semantics. I understand that this compiler may prefer to prevent copying through RVO regardless of using const& here. But does the const& lvalue value have any effect on non-t21> return values ​​in terms of copy prevention? And I specifically ask a question about pre-C ++ 11 compilers, before moving semantics.

My assumption is that either the compiler implements RVO or not, and that the expression lvalue should be const& does not indicate or does not create a situation without copying.

Edit

I specifically ask if const& uses a copy here, and not the lifetime of a temporary object, as described in the "most important Const"

Further clarification of the issue

It:

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

different from this:

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

in terms of copy reduction ? Or does this not affect whether the compiler can reduce copies, for example, through RVO?

+5
source share
2 answers

Hey, so your question is:

"When a function returns an instance of a class by value and assigns it a reference to a constant, does this prevent the copy constructor from being called?"

Ignoring the life time of a temporary, as this is not a question that you ask, we can understand what is happening by looking at the assembly. Im using clang, llvm 7.0.2.

Here is something standard. Come back by value, nothing unusual.

Test a

 class MyClass { public: MyClass(); MyClass(const MyClass & source); long int m_tmp; }; MyClass createMyClass(); int main() { const MyClass myClass = createMyClass(); return 0; } 

If I compile with "-O0 -S -fno-elide-constructors", I get this.

 _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 

We consider only the calling code. Not interested in implementing createMyClass. Thats compiled elsewhere. Thus, createMyClass creates the class inside the temporary one and then is copied to myClass.

Simples.

What about const ref version?

Test b

 class MyClass { public: MyClass(); MyClass(const MyClass & source); long int m_tmp; }; MyClass createMyClass(); int main() { const MyClass & myClass = createMyClass(); return 0; } 

The same compiler options.

 _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 

There is no copy constructor and, therefore, a more optimal right?

What happens if we disable "-fno-elide-constructors" for both versions? Saving -O0.

Test 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 removed the copy constructor call.

Test 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 

Test B (assign to reference const) is the same as before. Now it has more instructions than Test A.

What if we set the optimization to -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 

Both source files turn into this when compiling with -O1. They lead to the same assembler. This is also true for -O4.

The compiler does not know about the contents of createMyClass, so it cannot do anything to optimize.

With the compiler that I use, you do not get a performance boost from assigning const ref.

I imagine a similar situation for g ++ and intel, although it is always good to check.

+1
source

Semantically, the compiler needs an accessible copy constructor on the call node, even if later, the compiler returns the call to the copy constructor - that optimization is performed later at the compilation stage after the semantic analysis phase.

After reading your comments, I think I better understand your question. Now let me answer it in detail.

Imagine a function has this return statement:

 return items; 

Semantically, the compiler needs an accessible copy constructor (or move-constructor) here, which can be removed. However, just for the argument, suppose it makes a copy here, and the copy is stored in __temp_items , which I expressed as follows:

 __temp_items <= return items; //first copy: 

Now on the call site, suppose you did not use const & , so it becomes the following:

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

Now that you see yourself, there are two copies. Compilers are allowed to remove both of them.

However, your actual code uses const & , so it becomes as follows:

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

Now semantically there is only one copy that can still be undone by the compiler. As for the second copy, I will not say that const& "prevented" it in the sense that the compiler optimized it, and not the language starting with.


But interestingly, no matter how many times the compiler makes copies upon return, or excludes them from them (or all) a few, the return value is temporary. If so, how is attached to temporary work? If this is also your question (now I know that this is not your question, and then save it in such a way that I do not have to erase this part of my answer), then yes, it works, and it is guaranteed by the language.

As explained in detail in the article the most imporant const , if a const link is tied to a temporary one, then the temporary lifetime extends to the volume of the link, and it does not depend on the type of object.

In C ++ 11, there is another way to extend the lifetime of a temporary one, which is an rvalue reference:

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

It has the same effect, but the advantage (or the disadvantage depends on the context) is that you can also change the content.

I personally prefer to write this as:

 auto && things = getThings(); 

but then this is not necessarily rvalue-reference - if you change the return type of the function to return a link, then things be bound to lvalue-reference. If you want to discuss this, then this is a completely different topic.

+2
source

All Articles