Невозможно решить мою ошибку компилятора наследования алмазов?

Состав

Я создал проблему наследования алмаза. Это выглядит так

диаграмма наследования

Я думал, что достаточно хорошо понял виртуальное наследование, однако теперь я думаю, что я немного не понял его.

  • Насколько я понимаю, виртуальное наследование указывает компилятору игнорировать любые данные или функции-члены, которые появляются дважды с одним и тем же именем в результате шаблона наследования ромба, поэтому в производном классе будут содержаться только не виртуальные унаследованные компоненты.

  • Однако теперь я считаю, что такое понимание того, как компилятор реализует наследование, неверно.

У меня есть 2 ромбовидных шаблона наследования в моей иерархии наследования. Они отмечены с помощью прилагаемых примечаний.

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

Использование

Предполагаемое использование - std::list<GUIObject*> создано. Все объекты графического интерфейса должны поддерживать Draw и ProcessEvent. Не все объекты графического интерфейса будут содержать контейнер, содержащийся внутри SingleLineBuffer.

Buffer и FileBuffer наследуются от SingleLineBuffer, чтобы изменить поведение контейнера внутри SingleLineBuffer. (FileBuffer фактически только добавил новые функции ввода-вывода файлов.)

Можно создать экземпляр одного из буферов, но я этого не делаю в контексте, с которым работаю.

Невозможно создать экземпляр любого из абстрактных GUI* классов. Если подумать, то, вероятно, должен быть дополнительный абстрактный базовый класс ниже GUIMultilineTextEntry, который наследуется от FileBuffer.

Фактические объекты, экземпляры которых пользователь может создать, - это Label, Inputbox и Textbox. Я собираюсь добавить больше в будущем, например, многострочный ярлык. Вероятно, это должно быть унаследовано от базового класса, унаследованного от Buffer и GUITextObject.

Эта структура наследования быстро стала довольно сложной. Я писал его по ходу дела, руководствуясь тем, что мой код велел мне делать. Например, я написал класс Textbox, а затем сказал, что контейнер в Textbox по сути такой же, как контейнер в Label, поэтому они должны наследовать от общего объекта. Разница заключалась в том, что текстовое поле имеет файловый ввод-вывод, продиктован дополнительный шаг наследования, а текстовое поле может содержать символ новой строки в контейнере, поэтому здесь также продиктован дополнительный шаг наследования.

Вопросов

  • Можно ли решить эту проблему наследования?

  • Какие классы должны виртуально наследовать от других классов.

Мои попытки

  • Без виртуального наследования

Ошибка компилятора: (несколько версий)

error: request for member ‘Size’ is ambiguous
 status_text << "Save: " << static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->GetFilename() << ", " << static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->Size() << " bytes";

Size определен в SingleLineBuffer. Это невиртуальная функция, поскольку контейнер существует только в SingleLineBuffer, и поэтому Size написан для правильной работы как для Buffer, так и для FileBuffer.

  • Разбить ромб 1: Поместите virtual между GUITextObject и GUITextEntry, чтобы Size не опускался с помощью GUITextObject до того, как он будет переопределен в буфере. (Синяя отметка)

Ошибка компилятора (1):

error: no matching function for call to ‘GUITextObject::GUITextObject()’

Я могу исправить это, вызвав требуемый конструктор из GUIMultilineTextEntry. Я не понимаю, зачем это нужно. (Второй вопрос) Исправление показано ниже:

GUIMultilineTextEntry(const FontTextureManager& ftm, const int width, const int height)
    : GUITextEntry(ftm, width, height)
    , GUITextObject(ftm, width, height) // also call the constructor for the class which was inherited virtual in the previous step
{ ...

Это же исправление необходимо в Inputbox и Textbox.

Однако это приводит к следующей ошибке

error: cannot convert from pointer to base class ‘GUIObject’ to pointer to derived class ‘Textbox’ via virtual base ‘GUITextObject’
 static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->SetFilename(filename);

Я считаю, что могу решить эту проблему, используя dynamic_cast вместо static_cast, однако я не уверен, что это хорошая идея, поскольку я слышал, что динамическое приведение может значительно замедлить код.

  • Разбить алмаз 1, вторая попытка:

Я сделал вторую попытку решить проблему, фактически унаследовав от Buffer до SingleLineBuffer. (См. Красную точку) Однако, когда я это сделал, ошибка компилятора изменилась на

error: no unique final overrider for ‘virtual void SingleLineBuffer::SetText(const string&)’ in ‘Textbox’

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

  • Я пробовал похожие вещи, чтобы сломать ромб 2, но столкнулся с аналогичными ошибками компилятора.

Поскольку это теперь довольно длинный вопрос, я не буду перечислять здесь все подробности этой попытки.


person FreelanceConsultant    schedule 02.06.2018    source источник
comment
Кто-нибудь когда-нибудь захочет использовать GUITextObject в качестве своего SingleLineBuffer?   -  person Galik    schedule 02.06.2018
comment
@Galik Нет. Функции Draw и ProcessEvent будут чисто виртуальными в GUITextObject.   -  person FreelanceConsultant    schedule 02.06.2018
comment
Но будут ли они использовать его SingleLineBuffer методы?   -  person Galik    schedule 02.06.2018
comment
@Galik - Никто никогда не должен использовать SingleLineBuffer. Можно использовать FileBuffer для вкладок в текстовом поле, но кроме этого ни один из буферов не должен использоваться напрямую.   -  person FreelanceConsultant    schedule 02.06.2018
comment
@Galik Классы Label, Textbox и Inputbox будут использовать методы из SingleLineBuffer   -  person FreelanceConsultant    schedule 02.06.2018
comment
И Textbox будет использовать переопределенный метод SingleLineBuffer, который переопределяется в Buffer   -  person FreelanceConsultant    schedule 02.06.2018
comment
Захочет ли пользователь класса Label вызывать методы, унаследованные от SingleLineBuffer?   -  person Galik    schedule 02.06.2018
comment
виртуальное наследование указывает компилятору игнорировать любые данные членов или функции, которые появляются дважды с одним и тем же именем - на самом деле, в частности, сохраняется одна из нескольких версий, поэтому она будет присутствовать.   -  person Galik    schedule 02.06.2018
comment
@Galik, да, пользователь захочет вызывать функции: Insert, Delete, Size, GetText, SetText и т. Д.   -  person FreelanceConsultant    schedule 02.06.2018
comment
Похоже, вы используете множественное наследование для предоставления обоих интерфейсов, а затем, позже по цепочке наследования, реализации тех же самых интерфейсов.   -  person Galik    schedule 02.06.2018
comment
Я не совсем уверен, почему виртуальное наследование не работает для вас, но я никогда не строю свои иерархии наследования так сложно. Мне кажется, что GUITextObject - единственный текстовый компонент графического интерфейса, который должен наследовать от SingleLineBuffer, и что SingleLineBuffer должен быть чисто виртуальным интерфейсом.   -  person Galik    schedule 02.06.2018
comment
Тогда, возможно, FileBuffer следует не наследовать от Buffer, а быть отдельным типом реализации. После этого я, вероятно, использовал бы композицию, чтобы текстовые объекты графического интерфейса содержали соответствующую производную буфера в качестве средства реализации SingleLineBuffer части своего интерфейса. .   -  person Galik    schedule 02.06.2018
comment
Я имею в виду, что я не считаю себя абсолютным экспертом в этом вопросе, но вы, похоже, злоупотребляете множественным наследованием и, возможно, используете наследование, где композиция была бы более гибкой и подходящей.   -  person Galik    schedule 02.06.2018
comment
Композиция превыше наследования. Значит наследование есть. Мне это кажется ложным: TextBox - это SingleLinebuffer. Время переделывать. Или, по крайней мере, переименуйте классы, чтобы имена не содержали ложных заявлений.   -  person hyde    schedule 02.06.2018
comment
@Galik Проблема в том, что функции типов буферов не будут автоматически открываться пользователю. Например, нельзя было вызвать Label :: SetText   -  person FreelanceConsultant    schedule 02.06.2018
comment
Я думаю, что @hyde прав. Но все, что наследует от SingleLineBuffer, в какой-то момент будет вынуждено предоставить реализацию. Затем вы можете решить, собираетесь ли вы использовать Buffer или FileBuffer в качестве элементов данных для выполнения этой реализации (или, возможно, переключаться между ними во время выполнения ???).   -  person Galik    schedule 02.06.2018
comment
Кстати, это, вероятно, не подходящее место для рассмотрения такого рода вопросов из-за того, что он довольно широкий и в некоторой степени основан на мнениях. Трудно дать однозначный ответ без долгого обсуждения.   -  person Galik    schedule 02.06.2018
comment
Нет никаких жестких правил о том, как заниматься объектно-ориентированным дизайном, по большей части, это просто опыт и изучение проектов других людей, которые работают. В качестве руководства я считаю поучительным полностью отделить дизайн интерфейсов от дизайна реализаций. Делайте их как две отдельные фазы.   -  person Galik    schedule 02.06.2018
comment
Что вы подразумеваете под словом «не может быть виртуальным» на вашей диаграмме? Не то чтобы я вижу причину для наследования чего-либо практически от любого из GUI классов.   -  person Davis Herring    schedule 02.06.2018
comment
@DavisHerring Эти вещи объясняются в самих коротких заметках, а более подробная информация дается в вопросе.   -  person FreelanceConsultant    schedule 05.06.2018
comment
@ user3728501: Значит, вы имели в виду, что я пытался сделать это virtual? Я принял их за ограничения, что усугубило здесь значительную путаницу.   -  person Davis Herring    schedule 05.06.2018
comment
Опубликуйте минимальный воспроизводимый пример. То есть, один блок кода C ++ в том виде, в каком он скомпилирован, и список сообщений компилятора с номерами строк, которые ссылаются на блок кода sald.   -  person n. 1.8e9-where's-my-share m.    schedule 05.06.2018


Ответы (1)


Ответ на главный вопрос: нет. (Обычно в вопросе входит MCVE, но я полагаю, что это действительно ответ.) Что касается подробных вопросов:

  • Наследование должно быть virtual, если оно напрямую от общего предка («вершина ромба»), которого вам нужна только одна копия в полном объекте. (Итак, здесь вам нужно 4 virtual, просто считая сходящиеся стрелки.)
  • Вам необходимо вызвать конструктор каждой виртуальной базы в каждой конкретный класс, потому что класс-наследник инициализирует их напрямую.
  • Вы действительно не можете использовать static_cast из (или, как говорится, через) виртуальную базу, поскольку структура классов различается между экземплярами (из-за различий в других базовых классах). Однако стоимость одной dynamic_cast операции с графическим интерфейсом, несомненно, неизмерима.
  • Ваш анализ ошибок "уникального финального переопределения", вероятно, верен, но ответ - скорее cowbell virtual.
person Davis Herring    schedule 05.06.2018