资料的来源主要是:《c++ primer》,《Python核心编程》。
菱形继承关系
在类设计中,可能出现如下的菱形继承关系。
图中箭头表示派生方向。
Class A{};
Class B:public A{};
Class C:public A{};
Class D:public B, public C{};
在按值组合的继承中,类D的对象中会有两份类A的对象的副本。
C++的解决办法
c++采用虚拟继承,也就是按引用继承来解决这个问题。
虚线表示虚拟派生。
具体的实现方法如下:
Class A{};
Class B:virtual public A{}; 或者 Class B:public virtual A{}; (顺序无关)
Class C:virtual public A{};
Class D:public B, public C{};
构造函数的实现问题
在虚拟继承中,基类A的初始化工作就交给最终派生类(D)来完成。中间继承类B和C中对基类A的初始化会被抑制。不过最好的方法是,在B和C提供的public构造函数中,显式对类A完成初始化,然后提供一个构造函数的protected版本(不带参数),在该版本中不对类A做初始化。这样,在类D中就可以只显式初始化类A,而不显式初始化类B和C,编译器会调用不带参数的protected的构造函数来初始化类B和C,而且不设计对类A的重复初始化。
代码实现如下:
Class A
{
public:
A(parms){}
};
Class B:virtual public A
{
public:
B(parms):A(parms){}
protected:
B(){}
};
Class C:virtual public A
{
public:
C(parms):A(parms){}
protected:
C(){}
};
Class D:public B, public C
{
public:
D(parms):A(parms1){}
};
构造函数与析构函数的顺序
C++编译器首先检查直接基类的声明顺序,然后检查虚拟基类的出现次序。最后,按虚拟基类的出现次序,先初始化虚拟基类,然后按照非虚拟基类的声明顺序依次初始化其他类。
举例来说:
其构造函数调用顺序为:BCADEF
又如:
其构造函数调用顺序为:CBADE(C是按照直接继承顺序搜索到的第一个虚拟继承基类)
按成员初始化下的拷贝构造函数和按成员赋值下的拷贝赋值操作符的调用顺序与此相同。
析构的顺序与构造的顺序相反。
成员的可见性
- 在非虚拟继承下,菱形继承的任何非限定修饰的引用都是二义的
- 在最终派生类中,如果某个成员的一个来源是虚拟基类的成员,而另一个是后续派生类重写的成员,则调用的是后续派生类重写的成员,因为特化的派生类的实例的优先级高于共享的虚拟基类实例。但是,如果有两个后续的派生类同时重写了虚拟基类的成员,则最终派生类中的访问是二义性的
Python的处理方法
Python如果要调用父类的__init__方法,可以用super操作或者显式调用父类的初始化方法。
#!/usr/bin/env python
class A(object):
def __init__(self):
print 'A'
class B(A):
def __init__(self):
super(B, self).__init__()
print 'B'
class C(A):
def __init__(self):
super(C, self).__init__()
print 'C'
class D(B, C):
def __init__(self):
super(D, self).__init__()
print 'D'
d = D()
上述代码的输出如下:
A
C
B
D
共同的基类A只被初始化了一次。可见Python不存在共同基类被多次初始化的问题。
而在成员变量的引用顺序上,经典类(Python2.2版本之前)使用深度优先搜索,从左往右查找成员的第一次出现。如果不想引用该方法中指定的成员变量,只有使用非绑定的方法指定所引用的成员变量。在新式类(Python2.2版本之后),新的MRO(Method Resolution Order)方法被提出,开始采用广度优先搜索。