Search code examples
c++c++11move-semanticsvirtual-destructor

Defaulted destructor in base class disable move constructor in child class if there is a member


Why does defaulted (user declared) destructor in Base1 prevent generation of move constructor/operator in Child1 class, but everything work fine when I move member data from Base (Base2) to Child (Child2) class?

struct Data {
    Data() {}
    Data(Data&&) noexcept { cout << "Move constructor" << endl; }
    Data& operator=(Data&&) noexcept {
        cout << "Move assign" << endl;
        return *this;
    }
    vector<int> vec;
};

struct Base1 {
    virtual void fun() { cout << "Base1::fun" << endl; }
    virtual ~Base1() = default;
    Data data;
};

struct Child1 : public Base1 {
    void fun() override { cout << "Child1::fun" << endl; }
};

struct Base2 {
    virtual void fun() { cout << "Base2::fun" << endl; }
    virtual ~Base2() = default;
};

struct Child2 : public Base2 {
    void fun() override { cout << "Child2::fun" << endl; }
    Data data;
};

int main() {
    Child1 c1;
    auto obj1 = std::move(c1);  // error

    Child2 c2;
    auto obj2 = std::move(c2);
}

My current understanding is that when I declare destructor as “default” in Base (BaseDel), then move constructor should be “deleted” in Base (BaseDel) and in Child (ChildDel) class. Is this correct? Member location shouldn’t matter, I think. If I do that explicit, I get expected error:

struct BaseDel {
    BaseDel() {}
    virtual void fun() { cout << "BaseDel::fun" << endl; }
    BaseDel(BaseDel&& st) = delete;
    virtual ~BaseDel() = default;
};

struct ChildDel : public BaseDel {
    ChildDel() {}
    void fun() override { cout << "ChildDel::fun" << endl; }
    Data data;
};

int main() {
    ChildDel cd;
    auto objd = std::move(cd);  // OK, expected error
}

Solution

  • The implicit move constructor is not (only) deleted, it is not declared in the first place when you have a user-declared destructor, as is the case with Base1 and Base2.

    Therefore the move constructor can never be considered in overload resolution and so auto obj1 = std::move(c1);, while it can call Child1's move constructor, needs to fall back to copy construction for the Base1 subobject.

    The implicitly-declared copy constructors of both Base1 and Child1 are defined as deleted, because Data's implicitly-declared copy constructor is defined as deleted, because Data has a user-defined move constructor. Therefore auto obj1 = std::move(c1); will fail with an error that the implicitly-declared copy constructor is deleted.

    For Base2 the copy constructor is not defined as deleted, because it doesn't have a Data member and so auto obj2 = std::move(c2); will call Child2's move constructor (which also uses Data's move constructor), but use the copy constructor for the Base2 subobject.