多重继承描述的是有多个直接基类的类。多重继承会带来两个主要的问题: ①从两个不同的基类继承同名方法;
②从两个或更多相关基类那里继承同一个类的多个实例。例如: class A
{
private:
string name;
int id;
public:
virtual void f();
...
};
class B:public A
{
private:
char ch;
public:
void f();
...
};
class C:public A
{
private:
double db;
public:
void f();
...
};
class D:public B,public C
{
public:
void f();
...
};
下面的将出现二义性:
D d;
A* a = &d;
通常这种赋值将把基类指针设置为派生对象中的基类对象的地址。但d中包含两个A对象 ,有两个地址可供选择,所以应使用类型转换来指定对象:
A* a1 = (B*)&d;
A* a2 = (C*)&d;
这将使得使用基类指针来引用不同的对象复杂化。 当C++引入多重继承的时候,它引入了一种新技术----虚基类,是多重继承成为可能。虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。例如,通过在类声明中使用关键字virtual,可以使A被用作B和C的虚基类(virtual和public的次序无关紧要):
class B:virtual public A{...};
class C:virtual public A{...}; 使用虚基类时,需要对类构造函数采用一种新的方法。对于非虚基类,惟一可出现在初始化列表中的构造函数是即时基类构造函数。但这些构造函数可能需要将信息传递给其基类。例如下面的,
class base{...};
class derived1:public base{...};
class derived2:public derived1{...};
derived2类的构造函数只能调用derived1类的构造函数,而derived1类的构造函数只能调用base类的构造函数。 如果A是虚基类,那么这种信息自动传递将不起作用。例如,对于下面的多重继承构造函数:
D(const A& a,char c,double b)
:B(a,c),C(a,b){} 存在的问题是,自动传递信息时,将通过2条不同的途径(B和C)将a传递给A对象。为避免这种冲突,C++在基类是虚拟的时候,禁止信息通过中间类传递给基类。因此,上述构造函数将初始化成员ch和db,但a中的信息将不会传递给对象A。不过,编译器必须在构造派生对象之前构造基类基类对象组件;在上述情况下,编译器将使用A的默认构造函数。如果不希望默认构造函数来构造基类对象,则需显式地调用所需的基类构造函数。因此,构造函数应该是这样:
D(const A& a,char c,double b)
:A(a),B(a,c),C(a,b){}
注意,这种用法是合法的,对于虚基类,必须这样做,但是对于非虚基类,则是非法的。 究竟选用哪个方法?
对于单继承来说,让派生方法调用基类的方法是可以的,例如,对于B中的方法f():
void B::f(){ A::f();}
但是这种方法对于D范例无效。方法:
void D::f(){B::f();}将无效,因为它忽略了C组件。可以通过同时调用C版本的f()来补救:
void D::f(){B::f();C::f();}但是这样做很可能会做一些同样的工作。所以如何解决呢?使用模块化方式,而不是递增方式,即提供一个只显式A组件的方法和一个只显示B组件或C组件的方法,然后在D方法中将组件组合起来。
混合使用虚基类和非虚基类:当类通过多条虚拟途径和非虚拟途径继承某个特定的基类时,该类将包含一个表示所有的虚拟途径的基类子对象和分表表示各条非虚拟途径的多个基类子对象。 如果类从不同的类那里继承里两个多更多的同名成员,则使用该成员名时,如果没有用类名进行限定,将导致二义性。但如果使用的是虚基类,则这样做不一定会导致二义性。在这种情况下,如果某个名称优先于其他所有名称,则使用它时,即便不使用限定符,也不会导致二义性。 一个成员名如何优先于另一个成员名呢?派生类中的名称优先于直接或间接祖先类中的相同名称。例如,下面的定义中: class B
{
public:
short q();
...
};
class C:virtual public B
{
public:
long q();
int omb();
...
};
class D:public C
{...};
class E:virtual public B
{
private:
int omb();
...
};
class F:public D,public E
{
...
}
类C中的q()定义将优先于类B中的q()定义,因为类C是从类B派生而来的。因此,F中的方法可以使用q()来表示C::q()。而任何一个omb()定义都不优先于其他omb()定义,因为C和E都不是对方的基类。所以,在F中使用非限定的omb()将导致二义性。 虚拟二义性规则与访问规则无关,也就是说,即使E::omb()是私有的,不能再F类中直接访问,但使用omb()仍将导致二义性。同样,即使C::q()是私有的,它也将优先于B::q()。在这种情况下,可以在类F中调用B::q(),但如果不限定q(),则将一位着要调用不可访问的C::q()。