C++虚函数

一:虚函数

C++继承的难点,不在继承,而是在虚函数。之所以会有“虚函数”这个概念,是因为先有了“继承”这个概念,然后才出现了灵活多变的“虚函数”。可以说,没有继承,就没有虚函数。

简单地说,虚函数是C++用于实现多态的机制。

函数前面加一个virtual,就可以通过父类指针随意地调用各个子类的同名函数,用一个指针实现了不同的运行效果,达到多态的妙处,这就是常说的虚函数的主要用途。

二:静态联编和动态联编

联编是一个程序把自身各个部件(源代码、资源、相关的库和模块等)彼此关联的过程。

通常说的联编是静态联编,可以分为编译和链接两个阶段,编译就是把程序的源代码通过C++编译器转换成指定的中间代码(如汇编代码);链接就是把中间代码和资源等转换成目标二进制可执行代码。静态联编是早期联编,是在程序运行起来之前就已经完成的了。通常使用的函数就是静态联编完成的。这就是通常说的“最后是谁的指针,就运行谁的函数”。

动态联编是程序运行时进行的联编工作,C++规定动态联编是在虚函数的支持下实现的。

三:父类的析构函数必须是虚的析构函数

如果一个基类的析构函数是虚的析构函数,则它的子类不需要加上关键字virtual也一样是虚的析构函数。把析构函数声明为虚的析构函数的目的在于,使用delete删除一个对象时能保证析构函数被正确的执行。因为设置了虚的析构函数后,能确保用动态联编的方式选择正确的析构函数。

四:构造函数不能是虚函数

1.从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题来了,如果构造函数是虚函数的,就需要通过vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

2.从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,使用虚函数也没有实际意义。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数,而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

3.构造函数不需要是虚函数也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。

4.从实现上来看,vbtable在构造函数调用后才建立,因而构造函数不可能成为虚函数。从实际含义来看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数),而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数。

5.当一个构造函数被调用时,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE。如果函数调用使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后的VTABLE(所有构造函数被调用后才会有最后的VTABLE)。