2011-11-16 96 views
5

我阅读有关继承的,我有一个重大的问题,我一直没能解决几个小时:虚拟继承混乱

提供类Barvirtual函数的类,

class Bar 
{ 
    virtual void Cook(); 
}; 

之间有什么不同:

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

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

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

+2

我不打算回答,因为题材并不真的值得这么浅薄的处理:但是没有'virtual',从'Bar'继承的每个类都会有它自己的'Bar'副本,'virtual'最派生的类将只有一个'Bar'的副本。 –

+0

尝试:[此搜索](http://stackoverflow.com/search?q=q =虚拟+继承+%5Bc%2B%2B%5D) –

+0

[在C++虚拟基类?](http://stackoverflow.com/questions/21558/in-c-virtual-base-class) –

回答

4

虚拟继承只有在类继承 Foo时才有意义。如果我定义以下内容:

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

然后最终对象将只包含的B一个拷贝,由两者 LR共享。如果没有virtualD类型的对象将包含 B两个副本,一个在L中,一个在R中。

有一些论点认为,所有的继承应该是虚拟的(因为 在它有所作为的情况下,那就是你最需要的时间)。然而,在实践中,虚拟继承是昂贵的,并且在大多数情况下是不必要的:在一个设计良好的系统中,大多数继承只是从一个或多个“接口”继承的具体类。这样一个具体的类通常不会被设计成从本身派生的 ,所以没有问题。但是有一些重要的例外:例如,如果您定义了接口,然后定义了接口的扩展,那么扩展应该从基本接口几乎继承 ,因为具体实现可能希望 实现多个扩展。或者,如果您正在设计mixin,其中某些类仅实现接口的一部分,并且最终的 类继承了其中几个类(每个接口的一部分都有一个)。最后,该criteron是否继承几乎 与否并不太难:

  • 如果继承是不公开的,它可能不应该是虚拟 (我从来没有见过一个例外),否则

  • 如果类没有被设计成为一个基类,没有必要 虚拟继承,否则

  • 继承应该是虚拟的。

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

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

5

功能上明智的是两个版本没有太大的区别。在继承的情况下,每个实现通常添加(vptr类似)指针(与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' 
}; 
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不是虚拟基类,则在D的实例中将有两个A子对象。

+0

“_在这种情况下,没有区别。”,直到你尝试使用'static_cast'! – curiousguy

0

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

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

+0

“_这可能会导致模糊,而编译._”这**会导致歧义_iff_你尝试'C'是一个'基地'。 – curiousguy