I'm having trouble understanding why my declaring a destructor in my class doesn't delete the implicitly declared move constructor as is specified in this documentation, where it says :
If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
- there are no user-declared copy constructors;
- there are no user-declared copy assignment operators;
- there are no user-declared move assignment operators;
- there is no user-declared destructor.
then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature
T::T(T&&).
Note that the the copy constructor, copy assignment and move assignment should all be deleted too if I define my own destructor (hence the rule of 5)
Here's a small code sample to demonstrate my point :
class Test
{
public:
~Test() {}
protected:
int a = 5;
};
void main()
{
Test t1;
Test t2 = std::move(t1); //shouldn't work (note : if we have a copy constructor, will work even if the move constructor doesn't exist)
}
What am I missing? I'm sure it's obvious but I can't seem to find the documentation that explains the aforementioned behavior. I run my code on Visual Studio 2022 with C++20.
I discovered the aforementioned behaviour after having to create a virtual destructor in one of my base class and realizing that I didn't have to redefine all the copy&move constructors/assignments as I thought I'd have to.
Also, it's not 100% clear to me, why defaulting any of the copy/move constructors/assignments specifically with the keyword default requires in theory to redefine them all (+destructor) ? What's the incentive behind that choice if it's just defaulted ?
Thanks in advance.
Yes. The docs you quote are correct. There is no move constructor generated by the compiler, because you declared a destructor.
What you observe is not a contradiction to the documentation you quote. However, there are more special members that get generated by the compiler. From cppreference:
And thats what you see.
Test t2 = std::move(t1);calls the copy constructor.If you delete the copy constructor then there is no move constructor generated too:
results in:
I can only try to explain you the reasoning behind this. I think it is largely historic. From my limited understanding I would say that the original rules are overly optimistic in letting the compiler generate the special members. Often they don't do the right thing.
Consider how things were before move semantics. The rule of 3 says that if you implement any of the special members you need to define them all. This is not reflected by the original rules of when the compiler generates them. The copy constructor is generated even when you implement a custom destructor. And this is one major reason why we need the rule of 3, because the rules for when the compiler generates the 3 was a little too optimistic and often results in broken code (when the rule is not followed by the programmer. It is not followed by the compiler).
Now with move semantics things got more "right". The rule of 5 says that if you implement one you probably also need to implement the others. And that is now also reflected in the rules of when the compiler generates the move constructor.