(Почему) Требуется ли вызов конструктора виртуального базового класса в чисто виртуальном производном классе? - PullRequest
1 голос
/ 22 марта 2019

У меня есть иерархия классов со структурой diamond и базовым классом без конструктора по умолчанию , поскольку он имеет ссылочные элементы.

Код выглядит следующим образом:

class Base
{
public:
    Base( CustomType& obj ) : refObj_( obj ) {}
    virtual ~Base() {}
private:
    CustomType& refObj_;
};

class Left : public virtual Base
{
public:
    Left() {}; // ERROR: Compiler requests calling Base's constructor
    virtual void leftsMethod() = 0;
};

class Right : public virtual Base
{
public:
    Right( CustomType& obj ) : Base( obj ) {}; // Compiles, but Base( obj ) never gets called here
    virtual void rightsMethod() = 0;
};

class Bottom : public Left, public Right
{
public:
    Bottom( CustomType& obj ) : Base( obj ), Left(), Right( obj ) {}
};

Редактировать : Добавлен виртуальный базовый деструктор (в оригинальном коде есть)

Обратите внимание, что Левый и Правый являются чисто виртуальными классами, т.е. обстоятельства их конструкторы будут вызывать конструктор Base . Это связано с виртуальным наследованием, подразумевая, что самый производный класс Bottom вызывает Base конструктор.

Мой вопрос: почему я все же должен вызвать конструктор Base в списке инициализации Left ? Я бы хотел избежать этого и передать ссылку непосредственно из конструктора Bottom только в Base . (Я использую компилятор MSVC 2010 "half C ++ 11".)

Есть ли другое решение для этой комбинации виртуального наследования и ссылок в базовом классе?

Ответы [ 3 ]

3 голосов
/ 22 марта 2019

У вас нет конструктора по умолчанию для Base, но Left все еще нужно инициализировать один при создании объекта Left (который не является Bottom).

Проблема в том, что у вас есть ссылка в Base, что означает, что у вас нет простого конструктора.

Я бы сказал, что у вас большая проблема с дизайном, поскольку obj инициализируется Right, поэтому последний должен содержать obj, а не Base.

Также, где находится виртуальный деструктор для Base?

class Base
{
public:
    Base() = default;
    ~Base() {}
};

class Left : public virtual Base
{
public:
    virtual void leftsMethod();
};

class Right : public virtual Base
{
    int& refObj_;
public:
    Right( int& obj ) : refObj_( obj ) {}
    virtual void rightsMethod();
};

class Bottom : public Left, public Right
{
public:
    Bottom( int& obj ) : Right( obj ) {}
};
2 голосов
/ 22 марта 2019

Помимо проблем проектирования (которые явно существуют здесь), я полностью согласен с вашим рассуждением о том, что, поскольку Left является абстрактным классом, никакой конструктор Left никогда не должен будет вызывать какой-либо конструктор Base, и поэтому он является нечетным что это требуется.

На самом деле, я тестировал ваш код с несколькими версиями компилятора, и он компилируется очень хорошо с gcc 7.1 и далее, с clang 3.4.1 и и далее со всеми доступными msvc версиями.

Итак, я предполагаю, что это была просто ошибка в более ранних версиях компилятора. Кто-нибудь может это подтвердить?

Также обратите внимание, что если вы измените virtual void leftsMethod() = 0; на virtual void leftsMethod() {}, чтобы Left больше не был абстрактным, ошибка вернется, даже с последними версиями . И это вполне логично, так как теперь вы могли бы создавать экземпляр Left, и, таким образом, конструктору Left было бы до вызова одного из конструкторов Base.

Возможное решение

Если вы не можете переключиться на более новый компилятор, и если вы не можете изменить реализацию Base, это может быть рабочим решением для вас:

Определите фиктивный экземпляр где-нибудь CustomType. Затем вы можете предоставить «обязательные» конструкторы по умолчанию, например:

class CustomType{};

class Base
{
public:
    Base( CustomType& obj ) : refObj_( obj ) {}
private:
    CustomType& refObj_;
};

static CustomType dummy;

class Left : public virtual Base
{
protected:
    Left() : Base(dummy) {}; // note here
public:
    virtual void leftsMethod() = 0;
};

class Right : public virtual Base
{
protected:
    Right() : Base(dummy) {}; // and here
public:
    virtual void rightsMethod() = 0;
};

class Bottom : public Left, public Right
{
public:
    Bottom( CustomType& obj ) : Base( obj ), Left(), Right() {}
    virtual void leftsMethod() override {}
    virtual void rightsMethod() override {}
};

void test()
{
    CustomType c;
    Bottom b(c);
}

Это просто для того, чтобы компилятор был доволен, ваш аргумент, что эти конструкторы никогда не будут вызывать Base(dummy), по-прежнему сохраняется.

Обратите внимание, что Base действительно должен иметь виртуальный деструктор! Я не знаю, если вы просто не включили это здесь для краткости или оно действительно отсутствует. Если этого не произойдет, очень плохая идея построить иерархию классов поверх нее.

0 голосов
/ 22 марта 2019

Цитата из официальных документов может описывать ситуацию:

На практике это означает, что когда вы создаете конкретный класс, имеющий виртуальный базовый класс, вы должны быть готовы передать любые параметрыобязаны вызывать конструктор виртуального базового класса.И, конечно же, если в вашей наследственной структуре есть несколько виртуальных базовых классов, вы должны быть готовы вызвать все их конструкторы.Это может означать, что конструктору самого производного класса нужно больше параметров, чем вы могли бы подумать.

См. Этот FAQ .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...