虚拟继承混淆

时间:2011-11-16 08:08:28

标签: c++ inheritance c++11 virtual-inheritance

我正在阅读关于继承的内容,我有一个我无法解决数小时的重大问题:

鉴于课程Bar是一个具有virtual个功能的课程,

class Bar
{
    virtual void Cook();
};

有什么不同:

class Foo : public Bar
{
    virtual void Cook();
};

class Foo : public virtual Bar
{
    virtual void Cook();
};

?谷歌搜索和阅读的时间提供了大量有关其用途的信息,但实际上没有人告诉我两者之间有什么区别,只是让我更加困惑。

4 个答案:

答案 0 :(得分:5)

功能明智,两个版本之间没有太大区别。对于virtual继承的情况,每个实现通常都会添加一个(vptr like)指针(与virtual函数的情况相同)。这有助于避免由于多重继承而导致生成多个基类副本(diamond inheritance问题)

此外,virtual继承委托调用其基类的构造函数的权限。例如,

class Bar;
class Foo : public virtual Bar
class Other : public Foo  // <--- one more level child class

所以,现在Bar::Bar()将直接从Other::Other()调用,并且将被置于其他基类中的第一位。

委派功能有助于在C ++ 03中实现final class(Java中)功能:

class Final {
  Final() {}
  friend class LastClass;
};

class LastClass : virtual Final {  // <--- 'LastClass' is not derivable
...
};

class Child : public LastClass { // <--- not possible to have object of 'Child'
};

答案 1 :(得分:4)

虚拟继承仅在要继承类时才相关 Foo。如果我定义以下内容:

class B {};
class L : virtual public B {};
class R : virtual public B {};
class D : public L, public R {};

然后最终对象将只包含B的一个副本,由两者共享 LR。如果没有virtual,则D类型的对象将包含 两份B,一份L,一份R

有一些论点认为所有继承都应该是虚拟的(因为 在它产生影响的情况下,这就是你最想要的 时间)。然而,在实践中,虚拟继承是昂贵的,并且 在大多数情况下,没有必要:在设计良好的系统中,大多数情况下 继承只是一个继承自一个或一个的具体类 更多“接口”;这样一个具体的类通常不是设计的 源于自身,所以没有问题。但重要的是 例外:例如,如果您定义了一个接口,那么 扩展接口,扩展应该虚拟继承 从基础接口,因为具体实现可能想要 实现几个扩展。或者如果你正在设计mixins,那么 某些类只实现接口的一部分,最后一部分 class继承自其中几个类(每个类都有一个) 接口)。最后,关于是否继承虚拟的标准 与否并不太难:

  • 如果继承不公开,则可能不应该是虚拟的 (我从未见过例外),否则

  • 如果该类不是设计为基类,则不需要 虚拟继承,否则

  • 继承应该是虚拟的。

有一些例外,但上述规则错误 安全;即使在以下情况下,它通常也是“正确的”继承 虚拟继承不是必需的。

最后一点:必须始终初始化虚拟基础 派生类,直接继承的类(并声明它 继承是虚拟的)。然而,在实践中,这不是问题。 如果你看看虚拟继承有意义的情况,那就是 总是从接口继承的情况,它将不包含 数据,因此具有(仅)默认构造函数。如果你发现自己 实际上从具有构造函数的类继承 争论,现在是时候提出一些关于设计的严肃问题了。

答案 2 :(得分:3)

在这种情况下,没有区别。虚拟继承与派生类

共享超类子对象实例有关
struct A
{
  int a;
};

struct B : public virtual A
{
  int b;
}

struct C : public virtual A
{
  int c;
};

struct D : public B, public C
{
};

a的实例中有一个成员变量D的副本;如果A不是虚拟基类,则A实例中会有两个D子对象。

答案 3 :(得分:0)

虚函数是一个函数,它可能在派生类中有不同的实现(尽管它不是必须的)。

在上一个示例中是虚拟继承。想象一下,你有一个派生自基类的两个类(A和B)(我们称之为'Base')。现在想象一下从A和B派生的第三个C类。如果没有虚拟继承,C将包含两个'Base'副本。这可能会在编译时导致歧义。虚继承的重要一点是,'Base'类构造函数(如果有的话)的参数必须在C类中提供,因为来自A和B的调用将被忽略。