Sonntag, 11. November 2012

[C++] Moving own classes in standard container

Question
Got a question from a friend yesterday.
He had the following codes: 

The question was: "Why is the destructor called two times?"

The code is simple.
You create an object on stack and use the C++11 function move to move the object into a queue or a vector. In the end you remove that element from the container.

So you have:
- one call to constructor when the object is build
- one call to move constructor when the object is moved into the container
- and two calls to the destructor

Thinking of move there should be no construction. And where there is no construction there should be no destruction. Right?

Wrong!

Move is like a box. Or like gift wrap.
You put the present inside the box or gift wrap and give it to somebody. That one will probably take the gift out of the wrap and destroy the wrap.

That is what move is doing. You are giving an object to another object and that takes what it needs. Afterwards the remains of the old object is destroyed.

Demonstration-Code
I wrote a bit of code to demonstrate it:

There are to simple classes, with and without move-constructor, and a class holding a unique_ptr which forces to include a move-constructor when using move. Normally a class has a default move-constructor. It would move all members of the other object to this by calling their move-constructors. In this case the unique_ptr has not such thing. Clang shows messages like:
"note: function has been explicitly marked deleted here"
and
"error: call to implicitly-deleted copy constructor"

GCC shows:
"error: use of deleted function"
and 
"implicitly deleted because the default definition would be ill-formed"

The handwritten move-constructor just moves the unique_ptr over.
And here is the point where you see that move is a wrapper. With the move-constructor and "empty" object is created and then all data from the given object is cut out and taken over. After the move-constructor is done the destructor of the other object is called. In the output you can see the destruction of an FooBar object with a nullptr unique_ptr shown as upT->id[].


Just a hint:
Don't initialize members in the class definition, like I did for "id". As you can see those initializations are done for every constructor. Also for the move-constructor where you normally would take over data from somewhere else. Especially for bigger data structures it's a waste. Just imagine creating a new object for the unique_ptr at line 51 just to throw it away in the move-constructor. 
On the other hand there are some use-cases where an initialization inside the class definition is good. But those aren't many.


To demonstrate the move with those classes I used the standard containers vector and queue. They have methods like push and emplace to take over objects. The objects are either real objects from stack or unique_ptr.


Demonstration-Output
Using push and stack objects results in the use of the move-constructor. A lot of constructor and destructor calls can be seen. Especially the vector without a reserved size has a lot more of them. 
Emplace with stack objects and push and emplace with unique_ptr are much more efficient. Doing emplace with a stack object creates the object inside the container. Doing push creates the object first and then moves it inside the container. That's way there are to constructor (normal and move) calls. So emplacing would be a better way. 
Moving a pointer around is not a big deal. That goes for push and emplace. Comparing the use of pointer with the use of stack objects the pointer will win.
Why?
Because you don't need to care about the move-constructor of your own class. The different constructors for the unique_ptr are given. And it is just changing some pointer. In your own class you would need to take care of the different members and that each is taken over. If you add some member later you can easily forget to add it to your different constructors and create a bug there.


Results
Feed container with pointer and not stack objects.
Reserve space for the vector you gonna fill.
Prefer emplace over push.
Don't initialize members in the class definition.
Move.