У меня есть иерархия классов со структурой алмаз и базовым классом без конструктора по умолчанию, так как он имеет ссылочные элементы.
Код выглядит следующим образом:
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 ) {}
};
Редактировать: Добавлен виртуальный деструктор базы (он есть в исходном коде)
Обратите внимание, что Осталось и Верно являются чисто виртуальными классами, т.е. обстоятельствах их конструкторы будут вызывать конструктор Основание. Это происходит из-за виртуального наследования, подразумевающего, что наиболее производный класс Нижний вызывает конструктор Основание.
Мой вопрос: почему я все же должен вызывать конструктор Основание в списке инициализации Осталось? Я хотел бы избежать этого и передать ссылку непосредственно из конструктора Нижний только в Основание. (Я использую компилятор "половина С++ 11" MSVC 2010.)
Есть ли другое решение для этой комбинации виртуального наследования и ссылок в базовом классе?
@Питер Спасибо! Это ответ, который я искал.
В связи с этим лучше иметь виртуальную базу только с конструктором по умолчанию. Добавьте интерфейс (чистый виртуальный метод) вместо членов (не по умолчанию).
@Peter: Как человек, я нашел более запутанным отсутствие вызова инициализации. Этот чистый абстрактный класс не должен «вызывать» виртуальный базовый класс, это было бы хорошо, IMO.
@ Jarod42 Я согласен. Базовый класс - это устаревший код :(
@ Jarod42 - проблема с требованием, чтобы виртуальная база имела только конструктор по умолчанию, заключается в том, что базовый класс не контролирует, используется ли он в качестве виртуальной базы. Это производный класс, который указывает, является ли база виртуальной. Если код для базового класса внезапно не скомпилируется, потому что какой-то другой код использует его как виртуальную базу, это может сбить людей с толку. В любом случае, я давал свою интерпретацию того, почему поведение такое, какое оно есть, в ответ на вопрос. Если вы хотите возразить, что какое-то другое поведение лучше, свяжитесь с членом комитета по стандартизации и изложите свою позицию.
На самом деле, этот код прекрасно компилируется, начиная с лязг 3.4.1, gcc 7.1 и всех доступных (на godbolt.org) версий msvc. Проблема, описанная OP, возникает только в более ранних версиях. Кто-нибудь знает, что изменилось? @staerkHelmis, может быть, просто переключиться на более новую версию вашего компилятора?





У вас нет конструктора по умолчанию для 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 ) {}
};
Это не моя точка зрения, извините, я должен был это уточнить. Конструктор по умолчанию не подходит, потому что он может установить ссылку только с другим фиктивным членом. Даже этот конструктор по умолчанию никогда не будет вызываться, поэтому мне интересно, почему он должен быть реализован.
Но кто-то может наследовать от Left, и вам нужно будет инициализировать ссылку. C++ работает не так, как вы думаете.
Кто-то, кто наследует, скажем, LeftDrc, от Left, должен будет вызвать конструктор Base (явно или неявно). LeftDrc будет вынужден вызывать нестандартный конструктор Base, точно так же, как это необходимо сделать, если он напрямую наследуется от Base.
obj инициализируется Right". Нет, он инициализируется Bottom, это точка ОП. Проблема OP в том, что он должен писать абстрактные производные классы как Right, а не просто Left.
Хорошо, это проясняет вопрос. Все еще кажется мне плохим дизайном в первую очередь.
Цитата из официальных документов может описать ситуацию:
Practically speaking, this means that when you create a concrete class that has a virtual base class, you must be prepared to pass whatever parameters are required to call the virtual base class’s constructor. And, of course, if there are several virtual base classes anywhere in your classes ancestry, you must be prepared to call all their constructors. This might mean that the most-derived class’s constructor needs more parameters than you might otherwise think.
См. этот Вопросы-Ответы.
Причина сбоя в том, что при инициализации Base есть ссылка на инициализацию в Left. Так что нет конструктора по умолчанию. В противном случае все было бы хорошо.
Помимо проблем дизайна (которые явно существуют здесь), я полностью согласен с вашими рассуждениями о том, что, поскольку Left является абстрактным классом, никакой Left конструктор никогда не должен вызывать какой-либо Base конструктор, и поэтому странно, что это требуется.
На самом деле, я протестировал ваш код с несколькими версиями компилятора, и он компилируется прекрасно работает с gcc 7.1 и новее, с лязг 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в объявлении несвязанной функции-члена (т. е. переключение, является ли эта функция-член чисто виртуальной или нет) изменит то, как компилятор обрабатывает конструкторLeft(например, компилирует определение конструктора, если функция является чистой). virtual, но не компилируется, если функция виртуальная, но не чистая). Хотя, насколько мне известно, это не является формальным требованием процесса стандартизации, немногим разработчикам, включая членов комитета по стандартизации, нравится идея изменения кода, вызывающего поломку чего-то, не связанного с ним.