参考:
虚继承:http://blog.163.com/xiangzaihui@126/blog/static/166955749201182294317243/
不能被继承的类: http://blog.163.com/xiangzaihui@126/blog/static/166955749201182295845689/
虚表:
1. 虚继承
虚继承就是虚基类的使用;
引入虚基类的目的是为了解决类继承过程中产生的二义性问题;这种二义性问题常见于具有菱形继承关系的类中;
比如:有四个类:A、B、C、D;它们之间的继承关系是:B继承A,C继承A,D继承B和C;这就形成了一个菱形的继承关系;具有这种继承关系的图叫做有向无环图;
那么类D就有两条继承路径:D-->B-->A和D-->C-->A;而类A是派生类D的两条继承路径上的公共基类,那么这个公共基类就会在派生类D的对象中产生多个基类子对象D;这个时候在引用派生类D的对象时,就会产生明显的二义性;要解决这个二义性,就必须将这个基类设定为虚基类;
注意:
引进虚基类之后,派生类(子类)的对象中只存在一个虚基类的子对象;当一个类拥有虚基类的时候,编译系统会为这个类的对象定义一个指针成员,并让它指向虚基类的子对象;该指针被称为虚基类指针(vbptr);这个概念与虚函数表指针(vtptr)不同;在内存中,一般情况下,虚基类子对象在派生类对象中是放置在派生类对象所占内存块的尾部,这一点由编译器来决定的;
2. 为了初始化虚基类的子对象,派生类的构造函数要调用基类的构造函数.由于派生类的对象中只有一个虚基类子对象,那么就必须要保证虚基类的子对象只能被初始化一次,也就是说,虚基类的构造函数只能被调用一次;由于继承的层次可能会很深,C++规定:把真正创建对象时所指定的类称为是最派生类,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的;如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象;
从虚基类直接或间接继承的派生类的成员初始化列表中必须列出对该虚基类构造函数的调用,但是只有真正用于创建对象的那个最派生类的构造函数才会真正调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数的调用在实际执行中被忽略,这样就保证对虚基类的子对象只初始化一次;
C++又规定:在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,则虚基类的构造函数先于非虚基类的构造函数被执行;也就是说,执行虚基类构造函数的优先级要高于执行非虚基类构造函数的优先级;
在需要使用虚基类的场合,由于每一个继承类都必须包含初始化语句,而这些初始化语句仅仅只在最底层子类(最派生类)中才实际调用;这样就会使得某些上层子类得到的虚基类子对象的状态不是自己所期望的(因为自己的初始化语句被压制了/跳过了),所以,一般建议不要在虚基类中包含任何数据成员(不要有状态),只可以作为接口类来提供;
虚基类实例地址 = 派生类的vbptr + 派生类的vbptr到虚基类实例地址的偏移量;
2. 定义一个不能被继承的类
2. 虚函数
http://www.cnblogs.com/burellow/archive/2011/05/25/2056506.html
最后,总结一下关于虚函数的一些常见问题:
1) 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能,就不再解释了。
2) 构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还没有构造好, 多态是被disable的。 (编译器提示出错)
3) 析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。
4) 将一个函数定义为纯虚函数,实际上是将这个类定义为抽象类,不能实例化对象。
5) 纯虚函数通常没有定义体,但也完全可以拥有。
6) 析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
7) 非纯的虚函数必须有定义体,不然是一个错误。 (链接错误)
8) 派生类的override虚函数定义必须和父类完全一致。除了一个特例,如果父类中返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生。例如,在上面的例子中,在Base中定义了 virtual Base* clone(); 在Derived中可以定义为 virtual Derived* clone()。可以看到,这种放松对于Clone模式是非常有用的。
其他,有待补充。