A destructor, =default, and the move operations
Today's post is a bit special in two ways. First, I continue to talk about move semantics, and this is the first time that I have the same topic for my monthly post and the monthly C++ Insights YouTube episode. Oh, spoiler alert :-)
Today's topic is a part of move semantic I often get questions about in my classes. This is, what happens to the move operations of a class with a user-declared destructor? I often learn that people believe that =default
for the destructor is enough. We get all the special members back.
=default
is enought, isn't it?
That thought is reasonable, as =default
is more or less a way to tell the compiler to provide the default implementation for a certain member function.
Together with the destructors, this question usually comes up if the class in question serves as a base class. However, it is the same for derived classes.
Below is a piece of code that demonstrates the scenario.
1 2 3 4 5 6 7 8 9 10 |
|
In A, you can see the defaulted destructor. I left out the virtual part for simplicity reasons. This code compiles and runs fine. So this is the end of the post, =default
, and all is good?
My type-trait tells me =default
is enough
Well, we can look a bit deeper and very that we actually get a move and don't end up with a fallback copy. There is a type trait for this std::is_move_constructible_v
. Sounds perfect, right?
1 2 3 4 5 6 |
|
The code compiles with the static_assert
in B passing. So, this is the end of the post, right? That is the ultimate proof, Test
is move constructible.
Actually, the answer is still no. The behavior of std::is_move_constructible_v
is to check for move or copy! The type trait performs the same fallback as other move related code. It sounds it is time to fire up C++ Insights.
Your compiler knows the truth
If we put the initial example into C++ Insights, we can see the following transformed code:
1 2 3 4 5 6 7 8 9 10 11 |
|
Here you can see in C that the compiler only generates a copy constructor! But how does the resulting code look without a user-declared destructor?
Well, let's remove the user-declared destructor as shown below and transform this code.
1 2 3 4 5 6 7 8 9 10 |
|
The resulting code in C++ Insights is the following:
1 2 3 4 5 6 7 8 9 10 |
|
This time, the difference is that we look at a move constructor in D.
The take away
Either don't tamper with the destructor at all or remember to default the move operations in case you like to keep them alive. Once you do that, remember that you now need to do the same for the copy operations.
Andreas