通过不同的父类来自同一基类的多重继承真的是一个问题吗?

时间:2014-12-18 11:48:23

标签: c++ inheritance

我想在我的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. 删除例如: NameableObject。但是那样Level也会失去继承Object(我的第一个目标是:所有对象都继承自Object)。
  2. 我也可以从每个界面中删除: public Object 。但是,我会强制在许多引擎最终类(:public ObjectModel3DLine3DLevel)中手动键入PathSolver这样重构和创建新的最终类型都会更难。此外,每个接口保证从Object继承的信息都会丢失。
  3. 我想避免虚拟继承(进一步的抽象级别)如果则没有必要。但也许是(至少: public Object)?

5 个答案:

答案 0 :(得分:20)

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个不同的Object实例

虚拟继承中最棘手的部分是构造函数的调用(ref Virtual Inheritance in C++, and solving the diamond problem因为只有一个虚拟基类的实例由从它继承的多个类共享,虚构基类的构造函数不会被从它继承的类调用(这是调用构造函数的方式,当每个类都有自己的父类副本时),因为这意味着构造函数会多次运行。相反,构造函数由具体类的构造函数调用。 ... 顺便说一下,虚拟基类的构造函数总是在非虚拟基类的构造函数之前调用。这确保了从虚基类继承的类可以确保在继承类的构造函数中使用虚拟基类是安全的。具有虚基类的类层次结构中的析构函数顺序遵循与C ++其余部分相同的规则:析构函数以与构造函数相反的顺序运行。换句话说,虚拟基类将是最后一个被销毁的对象,因为它是第一个完全构造的对象。

但真正的问题是,衍生的可怕钻石通常被认为是一种错误的层次结构设计,并且在Java中明显被禁止。

但是恕我直言,如果所有的接口类都是纯粹的抽象类(只有纯虚拟公共方法)不是从Object继承而且所有的实现类都做了,那么C ++虚拟继承所做的事情并没有那么远。从Object中明确地继承。使用与否取决于你:毕竟它是语言的一部分......

答案 1 :(得分: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);

答案 2 :(得分: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{...};
...

答案 3 :(得分:2)

虚拟继承将在您的类层次结构中带来顺序。有几点需要注意:

  • 不要在层次结构中混合同一基类的虚拟/非虚拟继承,除非您真的知道自己在做什么。
  • 初始化顺序变得棘手。虚拟继承类的构造函数只会被调用一次。构造函数将从最派生的类中获取参数。如果未在Moveable3D中明确指定它,则将调用默认构造函数,即使Moveable为Object的构造函数指定了参数。因此,为了避免混乱,请确保在虚拟继承的所有类中仅使用DEFAULT构造函数。

答案 4 :(得分:1)

首先确保Model3D(使用多重继承)必须提供来自其'基类,Nameable以及Model3D。因为从您提供给您的班级的名称,我认为Nameable应该更好地作为'Model3D'的成员实施。

除了基类具有自己的数据之外,多重继承中的大多数问题都会出现。就像那时你必须决定基类是否是虚拟的等等......但是,你提到基类都是接口,即至少有一个纯虚函数。所以,我认为你可以避免许多多重继承的问题。