Search code examples
c++c++11constructormove-semanticsdiamond-problem

How should I call parent move constructor in diamond pattern?


Consider following diamond-like multiple inheritance:

class base;
class d1 : virtual public base;
class d2 : virtual public base
class d3 : public d1, public d2;

base is a move-only class (having a large move-only buffer). So are d1, d2 and d3. Move constructor of d1 and d2 call move constructor of the base.

Then what should do move constructor of d3? Calling both move-ctors of d1 and d2 results in crashs (since move constructor of base is called twice.

Here I have a minimum compilable instance of the problem:

#include <iostream>

struct moveonly {
    moveonly(): data(nullptr) {}
    moveonly(const moveonly &) = delete;
    moveonly(moveonly &&other) {
        this->data = other.data;
        other.data = nullptr;
    }
    ~moveonly() {
        if(data)
            delete[] data;
    }
    char *data;
};

class base {
    public:
        base() = default;
        base(const base &) = delete;
        base(base &&other) : d(std::move(other.d)) {  }
        virtual ~base() = default;
        int a;
        int b;
        moveonly d;
};

class d1 : virtual public base {
    public:
        d1() = default;
        d1(const base &) = delete;
        d1(d1 &&other) : base(std::move(other)) {  }
        int x;
        int y;
};

class d2 : virtual public base {
    public:
        d2() = default;
        d2(const base &) = delete;
        d2(d2 &&other) : base(std::move(other)) {  }
        int r;
        int s;
};

class d3 : public d1, public d2 {
    public:
        d3() = default;
        d3(const base &) = delete;
        // What should I do here?
        d3(d3 &&other) : d1(std::move(other)), d2(std::move(other)) {  }
        int p;
        int q;
};

int main()
{
    d3 child;
    child.d.data = new char[1024];
    for(size_t i = 0; i < 1024; ++i)
        child.d.data[i] = i * 2;
    d3 other_child = std::move(child);
    for(size_t i = 0; i < 1024; ++i) {
        std::cerr << other_child.d.data[i] << ' ';
    }
    std::cerr << std::endl;
    return 0;
}

Solution

  • As with all virtual inheritance, the virtual bases are initialized by the most-derived object, so:

    d3(d3 &&other)
        : base(std::move(other)),   // <== *you* initialize the base
          d1(std::move(other)),
          d2(std::move(other)) {}