8

As a C++ newbie I really have problems understanding the new Move-Constructor of C++11 and I hope someone can explain a specific situation I stumbled upon. Let's take this example code:

#include <iostream>

using namespace std;

class Model {
public:
    int data;
    Model(int data) : data(data) { cout << "Constructor" << endl; }
    Model(Model&& model) { cout << "Move constructor" << endl; }
    ~Model() { cout << "Destructor" << endl; }

private:
    Model(const Model& model);
    const Model& operator=(const Model&);

};

Model createModel(int data) {
    return Model(data);
}

int main(void) {
    Model model = createModel(1);
    cout << model.data << endl;
    return 0;
}

So I have created a createModel function which should return a model as a temporary rvalue and I want to assign it to an lvalue. I don't want the compiler to create copies of the Model object so I define the copy constructor as private and I do the same with the assignment operator to make sure no data is copied. After doing this the code correctly no longer compiles so I added the Move constructor and now it compiles again. But when I run the program I get this output:

Constructor
1
Destructor

So the Move Constructor was never called. I don't understand why I have to specify the move constructor to be able to compile the program when it is not used at all during runtime.

Is it because the compiler (GCC 4.8.2) optimizes the Move Constructor away? Or is there some other magic performed here?

So can someone please explain what exactly happens in the code above? The code does what I want but I really don't understand why.

kayahr
  • 20,913
  • 29
  • 99
  • 147
  • 3
    Search for "return value optimization" (RVO), this has been talked about a lot already (including here). โ€“ Mat Jul 13 '14 at 11:30

3 Answers3

7

There are two moves that could happen in your program:

  1. From the function to the return object.
  2. From the return object to model.

Both of these moves can be elided by the compiler for the same reason:

when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

There are other situations in which copy/move elision occurs (see ยง12.8/31 in C++11). Note that copy/move elision is entirely optional - the compiler doesn't have to do it.

Note that the compiler is allowed to optimize absolutely anything away as long as it doesn't change the behaviour of your program (under the as-if rule). The reason that copy/move elision is explicitly mentioned in the standard is because it might change the behaviour of your program if your copy/move constructors have side effects. The compiler is allowed to perform this optimization even if it changes the behaviour of your program. This is why your copy/move constructors should never have side effects, because then your program will have multiple valid execution paths.

You can pass the -fno-elide-constructors option to gcc to ensure that this optimization is never performed.

Holt
  • 36,600
  • 7
  • 92
  • 139
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • After compiling the code with the `-fno-elide-constructors` option I was finally able to see the effects of the Move Constructor and I was also able to play around with the move-assignment-operator and I think I finally understood the move-semantics of C++11 now. Thanks a lot! โ€“ kayahr Jul 13 '14 at 12:02
1

i think this is what you call copy elision (i.e. prevent the copy of an object) and directly use it. See: copy elision: move constructor not called when using ternary expression in return statement?

Community
  • 1
  • 1
cageman
  • 333
  • 2
  • 10
  • Not really the same problem since in your link the `return a;` statement call the `move` constructor, which is not the case here, and which is why the OP ask his question. โ€“ Holt Jul 13 '14 at 11:34
1

You are a "victim" of Return Value Optimization here.

Note this: "In C++, it is particularly notable for being allowed to change the observable behaviour of the resulting program".

EDIT: Hence, the compiler is allowed to apply the optimization, even though the side effects of the move-ctor (cout) have been changed.

namezero
  • 2,203
  • 3
  • 24
  • 37