Why does an explicitly defaulted destructor disable default move constructor?

Viewed 166

Why does an explicitly defaulted destructor disable default move constructor in the class? I know that it does, as explained in several existing answers. (e.g. Explicitly defaulted destructor disables default move constructor in a class )

I want to know why: what is the rationale for this, when it doesn't actually do anything that implies that the move constructor might need to have custom code? In fact, we recommend that people use =default rather than an empty body because that way the compiler knows it doesn't do anything beyond the automatic actions, exactly as if it had been automatically generated without any declaration.

To be really really clear, I know why defining a destructor with your own logic in it should suppress autogeneration. I'm pointing out that =default doesn't change anything as compared with letting the compiler implicitly generated it, so that reason does not apply here.

I recall >10 years ago there was a lot of discussion on what is the right way to specify it. I don't remember, if I ever learned, why it went the way it did in the end. Does anyone know of a compelling reason why this is a specific desirable feature in itself, or some technical reason why it ought to be this way?

demonstration: https://gcc.godbolt.org/z/86WGMs7bq

#include <type_traits>
#include <utility>
#include <string>
#include <iostream>


struct C
{
    std::string s;
    C () : s{"default ctor"} {}
//    ~C() = default;  // <<< comment this line out, and we see that original.s has its contents "stolen"
                       // <<< with this dtor declared, the original string is copied.
};

int main()
{
    C original;
    original.s = "value changed";
    C other { std::move(original) };
    using std::cout;
    cout << "original now: " << original.s << '\n';
    cout << "other is: " << other.s << '\n';
}

This rule is stated in cppreference and was mentioned the other day in a C++ conference video that was just posted, which reminded me of it.

1 Answers

I think the best rationale is consistency: an explicitly defaulted copy constructor (among other things) disables the implicit move constructor, so the simplest rule is that any declaration of a relevant special member function does so.

In turn, the reason for A(const A&)=default; to have this effect is that it adds expressive power: one can, without tedious and error-prone member initializer lists, define classes that hold onto their resources when “moved from” (to provide an exception to an otherwise sink interface). This despite the fact that =default provides no more information there than it does on a destructor: generally speaking, it makes sense to give these constructs special meaning precisely because they have no other effect. If they did have some other meaning, it wouldn’t be safe to assume that the programmer who used them wanted some other change of behavior in addition to their direct semantics.

On that subject, why encourage generally writing ~A()=default; or ~A() {}? Neither conveys any intrinsic information to the compiler, and the behavior with the implicitly declared destructor is often preferable.

Related