Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
}
like image 342
sorush-r Avatar asked Nov 05 '17 08:11

sorush-r


People also ask

Are move constructor automatically generated?

If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.

What is a move constructor?

A move constructor allows the resources owned by an rvalue object to be moved into an lvalue without creating its copy. An rvalue is an expression that does not have any memory address, and an lvalue is an expression with a memory address.

When move constructor is not generated?

The move constructor is not generated because you declared a copy constructor. Remove the private copy constructor and copy assignment. Adding a non-copyable member (like a unique_ptr ) already prevents generation of the copy special members, so there's no need to prevent them manually, anyway.


1 Answers

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)) {}
like image 79
Kerrek SB Avatar answered Oct 11 '22 08:10

Kerrek SB