2014-12-18 1 views
17

Я хочу реализовать иерархию наследования в моей C++ игровой движок, который имеет некоторые аналогии с Java:Является ли множественное наследование от того же базового класса через разные родительские классы действительно проблемой?

  • все объекты наследуют от класса Object,
  • некоторые функциональные возможности обеспечиваются интерфейсами.

Это просто аналогия, что дает мне некоторые преимущества (не является строгим 1: 1 Java: C++ отображения, которые не возможно).

Под «интерфейсом» я имею в виду здесь класс с чистыми виртуальными, общедоступными методами. Я знаю, что это не точное и точное определение.

Так что я сделал:

class Object{...}; 

/* interfaces */ 
class Nameable : public Object{...}; 
class Moveable : public Object{...}; 
class Moveable3D : public Moveable{...}; 
class Moveable2D : public Moveable{...}; 

class Model3D : public Moveable3D, public Nameable{...} 
class Line3D : public Moveable3D{...} 
class Level: public Nameable{...} 
class PathSolver : public Object{...} 

Как вы можете видеть, Model3D Теперь наследуется от Object через несколько способов:

  • Moveable3D -> Moveable -> Object,
  • Nameable -> Object.

И мой компилятор в VS2013 (VC++) предупреждает меня об этом.

Это проблема?

Если да, то как решить проблему, чтобы получить лучшую структуру наследования?

Я думал о двух направлениях, но оба имеют больше недостатков, то преимущества:

  1. сбросив наследования между, например, Nameable и Object. Но таким образом Level потерял бы свое наследство от Object тоже (и мой первый гол был: все объекты наследуют от Object класс).
  2. Я также мог бы опустить : public Objectс каждого интерфейса. Но таким образом я был бы вынужден вручную набирать :public Object в каждом из многих финальных классов двигателей (Model3D, Line3D, Level, PathSolver). Таким образом, рефакторинг и создание новых конечных типов будут сложнее. Кроме того, информация о том, что каждый интерфейс будет гарантированно наследовать от Object, теряется именно так.

Я хотел бы избежать виртуального наследования (один уровень абстракции дальнейшего) если это не нужно. Но может быть, это (по крайней мере, для : public Object)?

+1

@jxh Я не уверен, о чем вы просите. Но: любой 'Nameable' гарантированно будет' Object' (тот же для 'Moveable3D'). Но 'Moveable3D' не обязательно должен быть« Nameable »одновременно. Когда вы передаете 'Object *', вы не можете предположить, что это 'Nameable' или' Moveable3D', но вы можете, например. используйте 'dynamic_cast', чтобы проверить его. И, конечно, вы можете назначить любой другой тип «Object *» (это было бы лучше). ** Кроме того ** все интерфейсы имеют чистый виртуальный метод, поэтому вы не можете использовать их с помощью только 'new Nameable()', вам сначала нужно наследовать их. Это то, о чем вы просили? – PolGraphic

+4

Ваша текущая структура означает, что 'Model3D' имеет два экземпляра' Object'. Таким образом, если вы попытаетесь присвоить ему ссылку «Object», вы получите ошибку неоднозначности, если вы не укажете, к какому «объекту» следует обращаться (один к «Nameable» или к «Moveable3D»). Но если вы используете виртуальное наследование, у вас будет только один объект «Object» («Nameable» и «Moveable3D» - тот же экземпляр «Object»). – jxh

+0

Является ли 'Object' интерфейсом? – jxh

ответ

18

As said by Juraj Blaho Канонический способ решения этой проблемы - виртуальное наследование. Одной из реализационных моделей виртуального наследования является добавление записи в vtable классов с использованием виртуального наследования, указывающего на реальный уникальный объект из базового класса.Таким образом, вы получите следующее наследство графа:

   Model3D 
      /  \ 
    Moveable3D Nameable 
      |   | 
     Moveable  | 
      \  /
      Object 

То есть вы должны иметь:

class Nameable : public virtual Object{...}; 
class Moveable : public virtual Object{...}; 

Другие классы не нужны виртуального наследования


без виртуального наследования графа было бы

   Model3D 
      /  \ 
    Moveable3D Nameable 
      |   | 
     Moveable  | 
      |   | 
     Object Object 

с 2-мя различными экземплярами объекта

Самая сложная часть в виртуальном наследования является вызов конструкторов (ссылка Virtual Inheritance in C++, and solving the diamond problem) Поскольку существует только один экземпляр виртуального базового класса, который является общим для нескольких классов, которые наследуют от он, конструктор для виртуального базового класса не вызывается классом, который наследует от него (каким образом вызываются конструкторы, когда каждый класс имеет свою собственную копию своего родительского класса), поскольку это означает, что конструктор будет выполняться несколько раз. Вместо этого конструктор вызывается конструктором конкретного класса. ... Между прочим, конструкторы виртуальных базовых классов всегда вызывают перед конструкторами для не виртуальных базовых классов. Это гарантирует, что класс, наследующий от виртуального базового класса, может быть уверен, что виртуальный базовый класс безопасен для использования внутри конструктора наследующего класса. Порядок деструктора в иерархии классов с виртуальным базовым классом следует тем же правилам, что и остальные C++: деструкторы работают в противоположном порядке конструкторов. Другими словами, виртуальный базовый класс будет последним уничтоженным объектом, потому что он является первым объектом, который полностью сконструирован.

Но реальная проблема заключается в том, что это Dreadful Алмазные на деривации часто рассматривается как плохой дизайн иерархии и явно запрещено в Java.

Но IMHO, что виртуальное наследование C++ делает под капотом, не так далеко от того, что у вас было бы, если бы все ваши классы интерфейса были чистыми абстрактными классами (только чистые виртуальные общедоступные методы), не наследуемыми от Object, и если все классы реализации явно наследовал объект. Использование этого или нет зависит от вас: ведь это часть языка ...

+0

Отличный. Пока мне нравится, что вы отвечаете больше всего :) Иллюстрация проблемы, решения и «осознайте ... [когда вы используете это решение]», часть делает его полным ответом. Спасибо, сэр. – PolGraphic

+1

"явно запрещено в Java" Ну, Java не содержит множественного наследования вообще ... – idmean

1

Сначала убедитесь, что Model3D (который использует множественное наследование) должен обеспечивать интерфейс как от его базовых классов i.e Nameable а также Model3D. Поскольку из имени, которое вы предоставили своему классу, мне кажется, что Nameable следует лучше реализовать как член 'Model3D'.

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

+1

За исключением того, что у них нет указателей или ссылок на 'Object'. – jrok

+0

@ravi благодарит вас за предложение проверить возможность сдерживания vs наследования в моей модели. Это сложнее, чем то, что я поставил под вопрос (чтобы сделать его читаемым), но это прекрасный момент во многих местах моей структуры.Однако, я не думаю, что это лекарство во всех них. На данный момент я проголосовал - я не буду принимать его, чтобы дать шанс представить другие идеи :) – PolGraphic

4

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

class Object{...}; 

/* interfaces */ 
class Nameable : public virtual Object{...}; 
class Moveable : public virtual Object{...}; 
class Moveable3D : public virtual Moveable{...}; 
class Moveable2D : public virtual Moveable{...}; 

class Model3D : public virtual Moveable3D, public virtual Nameable{...} 
class Line3D : public virtual Moveable3D{...} 
class Level: public virtual Nameable{...} 
class PathSolver : public virtual Object{...} 

без виртуального наследования существует несколько экземпляров одного и того же основания в объекте, который будет причиной того, что вы не сможете подставить конкретный тип, где необходим интерфейс:

void workWithObject(Object &o); 

Model3D model; 
workWithObject(model); 
+0

Возможно, это решение здесь. Однако, я думаю, я могу уменьшить использование виртуального наследования до наследования от «Object». ** Какие недостатки могли бы принести мне использование виртуального наследования в обмен ** для решения проблемы? – PolGraphic

+1

@PolGraphic: Да, вы можете ограничить это наследованием от 'Object'. Современный подход для игрового движка C++ не должен использовать наследование, а использовать композицию (компонент/сущность системы). Но это звучит слишком не по теме для заданного вопроса. Некоторая информация, например: http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/ –

+0

спасибо :) И ссылка, которую вы указали, действительно стоит прочитать :) – PolGraphic

3

Да, это вопрос: В макете памяти Model3D, есть два подобъектов типа Object; один является подобъектом Movable3D, другой - Nameable. Таким образом, эти два подобъекта могут содержать разные данные. Это особенно проблема, если класс объекта содержит функциональные возможности, связанные с идентификацией объекта. Например, класс Object, который реализует подсчет ссылок, должен быть уникальным подобъектом всех объектов; объект, который имеет два разных подсчета ссылок, очень вероятно очень плохая идея ...

Чтобы избежать этого, используйте виртуальное наследование. Только виртуальное наследование, что требуется, из Object базового класса:

class Objec{...]; 

class Nameable : public virtual Object{...}; 
class Moveable : public virtual Object{...}; 
class Moveable3D : public Moveable{...}; 
class Moveable2D : public Moveable{...}; 

class Model3D : public Movable3D, public Nameable{...}; 
... 
+0

Спасибо. Я вижу, как виртуальное наследование решает проблему здесь. Но мне также любопытно - есть ли проблемы, что виртуальное наследование вместо «нормального» приводит к моей конкретной модели в обмене? Было бы здорово, если бы вы могли это отрицать или коротко назвать любым из них (если это возможно). Тем не менее, отлично реагировать на приятные объяснения :) – PolGraphic

+0

Единственный недостаток - это несколько циклов процессора дополнительной задержки при вызове указателей базовых классов виртуальных функций/понижающих прогнозов: когда вы опускаете без виртуального наследования, указатель настраивается на постоянное количество байтов; с виртуальным наследованием, поиск из таблицы vtable необходим для нахождения правильного смещения. Это все. – cmaster

+0

Спасибо. Вызов будет «длиннее» только для методов, которые наследуются практически от «Object» или для любого виртуального метода, объявленного в любом классе (например, метод, объявленный в «Moveable3D», когда только «Moveable» наследует фактически от «Object»)? – PolGraphic

2

Виртуальное наследование будет навести порядок в вашей иерархии классов. Несколько вещей, которые необходимо обратить внимание на:

  • Не смешивайте виртуальное/не виртуальное наследование для того же базового класса в своей иерархии, если вы действительно не знаете, что делаете.
  • Порядок инициализации становится сложным. Конструктор фактически унаследованного класса будет вызываться только один раз. Конструктор будет принимать аргументы из самого производного класса. Если вы не укажете его явно в Moveable3D, будет вызываться конструктор по умолчанию, даже если Moveable заданы аргументы для конструктора Object. Поэтому, чтобы избежать беспорядка, убедитесь, что вы используете только конструкторы DEFAULT во всех классах, которые вы наследуете практически.
Смежные вопросы