C++ copy constructor activation in return-by-vlaue
Asked Answered
A

2

2

I wasn't able to find a concrete answer for the following question:

Consider the following code:

Obj f() {
    Obj o2;
    return o2;
}

int main() {
    Obj o1 = f();
    return 0;
}

How many times is the copy constructor activated without compiler optimization?

In case there isn't move constructor, isn't it one time for copying o2 to the caller function and another time for constructing o1?

In case there is move constructor, isn't it one time for copying o2 to the caller function and another time for constructing o1 (2nd time is move const)?

Aga answered 30/1, 2021 at 20:39 Comment(9)
Can you show the definition of Obj?Ivanaivanah
It can depend on which version of C++ you are using.Sublime
@Brian How is that relevant?Aga
@FrançoisAndrieux Yeah I know, it wasn't the point of the question so I was just trying to write as less code as possible.Aga
@YanivK. It matters because we need to see whether Obj has a move constructor.Ivanaivanah
@Brian Right. in the case it has, it will be copy const and then move const?Aga
If Obj has a move constructor, edit this detail into the question, or just show the definition of Obj. The comments section is too short to give an answer.Ivanaivanah
@Brian Edited..Aga
There's a reason why I asked to see the definition of Obj. If you say "maybe it does have a move constructor, or maybe it doesn't", then it doubles the amount of work everyone has to do to answer your question, by addressing all cases. It's better to ask one question about a specific situation, then ask another question if there's another situation you want to know about as well.Ivanaivanah
C
3

Before C++17, yes there are two copy constructor calls (both of which can be elided even if they have side-effects). You can see this with -fno-elide-constructors in gcc/clang.

Since C++17 due to the new temporary materialization rules there is just one copy involved inside f (which, again, can be elided).


To be accurate all of them are moves, not copies.

Camphene answered 30/1, 2021 at 20:45 Comment(0)
I
6

C++03 and before

Obj is copied twice. Once by the return statement (constructing the return value), and once to initialize o1 by copying the return value.

C++11 and C++14

If Obj has a usable move constructor, it is moved twice and copied zero times. The return statement must use a move, even though the returned expression is an lvalue. This "move optimization" is mandatory due to a special rule in the language; o2 must not be copied, even when optimizations are disabled. A second move occurs when o1 is initialized.

If Obj has either no move constructor or an implicitly deleted move constructor, the copy constructor is used twice.

If Obj has an explicitly deleted move constructor, the program is ill-formed since the initialization of o1 tries to use the deleted move constructor.

C++17 and later

If Obj has a usable move constructor, it is moved once, when the return statement is executed. As mentioned above, it is mandatory for the compiler to use a move instead of a copy. The construction of o1 involves neither a copy nor a move. Rather, the return statement in f() initializes o1, without the involvement of a temporary. This is because of "guaranteed copy elision": the language requires the copy to be elided, even if optimizations are disabled. This is because f() is a prvalue, and a prvalue is not materialized (i.e., instantiated as a temporary object) unless it is necessary to do so. The "legal fiction" created by the standard is that f() actually returns a "recipe" for creating Obj, not an Obj itself. In practice, this can be implemented the same way that the (optional) return value optimization was implemented in earlier versions of the standard: the caller passes a pointer to o1 directly into f, and the return statement constructs Obj into this pointer.

If the move constructor of Obj is implicitly deleted or does not exist, the copy constructor will be used by the return statement, so there will be one copy and zero moves.

If the move constructor of Obj is explicitly deleted, the program is ill-formed as in the C++11/C++14 case.

In all cases

The copies/moves in the above situations can be optimized out. In cases that involve more than one copy/move operation, the compiler can optimize out any or all of them.

Ivanaivanah answered 30/1, 2021 at 21:19 Comment(0)
C
3

Before C++17, yes there are two copy constructor calls (both of which can be elided even if they have side-effects). You can see this with -fno-elide-constructors in gcc/clang.

Since C++17 due to the new temporary materialization rules there is just one copy involved inside f (which, again, can be elided).


To be accurate all of them are moves, not copies.

Camphene answered 30/1, 2021 at 20:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.