我正在阅读关于继承的内容,我有一个我无法解决数小时的重大问题:
鉴于课程Bar
是一个具有virtual
个功能的课程,
class Bar
{
virtual void Cook();
};
有什么不同:
class Foo : public Bar
{
virtual void Cook();
};
和
class Foo : public virtual Bar
{
virtual void Cook();
};
?谷歌搜索和阅读的时间提供了大量有关其用途的信息,但实际上没有人告诉我两者之间有什么区别,只是让我更加困惑。
答案 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
的一个副本,由两者共享
L
和R
。如果没有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的调用将被忽略。