5

我正在阅读有关继承的信息,但我遇到了一个几个小时都无法解决的主要问题:

给定一个类Bar是一个具有virtual函数的类,

class Bar
{
    virtual void Cook();
};

有什么区别:

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

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

? 几个小时的谷歌搜索和阅读得到了很多关于它的用途的信息,但没有人真正告诉我两者之间的区别是什么,让我更加困惑。

4

4 回答 4

5

功能方面,两个版本之间没有太大区别。在virtual继承的情况下,每个实现通常都会添加一个(vptrlike)指针(与函数的情况相同virtual)。这有助于避免由于多重继承而产生的多个基类副本(菱形继承问题)

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

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

因此, nowBar::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'
};
于 2011-11-16T08:14:42.303 回答
4

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

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

那么最终对象将只包含 的一个副本,由和B共享 。如果没有,类型对象将包含 的两个副本,一个在 中,一个在 中。LRvirtualDBLR

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

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

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

  • 继承应该是虚拟的。

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

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

于 2011-11-16T09:35:06.540 回答
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不是虚拟基类,AD.

于 2011-11-16T08:16:31.980 回答
0

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

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

于 2011-11-16T08:17:43.783 回答