一、编译器如何处理虚函数
二、使用虚函数的成本
三、有关虚函数的注意事项
- 编译器为每一个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针。这个数组被称为虚函数表
- 虚函数表中存储了为类对象进行声明的虚函数的地址。
- 如果派生类提供了虚函数的新定义,改新函数将保存新函数的地址,如果派生类没有重新定义虚函数,那么虚函数表保存函数原始版本的地址
- 不管类中包含的虚函数的个数有多少,都只给对象提供一个地址成员(隐藏成员),只是虚函数表的大小不同
- 虚函数属性会被继承,也就是说一个基类有虚函数表,那么派生类也会有一个虚函数表
- 如果一个虚函数只被声明,并没有定义(实现),那么在编译时,会报错
二、使用虚函数的成本
- 1、每个对象都将增大,增大量为存储地址的空间
- 2、对于每个类,编译器都将创建一个虚函数的地址表
- 3、对于每个函数调用,都需要执行查表操作
三、有关虚函数的注意事项
- 1、在基类中使用virtual声明的方法,在基类和所有的派生类中都是虚的
- 2、如果使用指向对象的引用或者指针调用虚方法,程序将使用对象类型定义的方法,而不使用为引用或者指针类型定义的方法。
- 3、如果定义了虚函数的类被当做基类,那么应将那些要在派生类中重新定义的类方法声明为虚的
- 4、构造函数不能是虚的---因为不符合继承的机制
- 5、析构函数应当是虚函数,除非类不做基类
- 6、友元函数不是类成员,因此也不能是虚函数,因为虚函数是要被子类重新定义的。
- 7、在派生类中重新定义的函数必须和基类的函数原型一致,包括参数,(不是函数重载),但当返回值是基类的指针或引用,则允许修改返回值为指向派生类的指针或引用。这种特性被称为返回类型协变。
- 8、如果基类声明被重载了,则应该在派生类中重新定义所有的积累版本。也就是说所有被重载的函数版本都需要被重新定义
如果一个类有一个或多个成员函数都是虚函数,那么编译器会为该类创建一个表,改表被称为虚函数表,对于每一个虚函数成员,虚函数表都有一个对应的指针,指向虚函数。(即虚函数表保存每一个虚函数的内存地址),每个指针指向其对应的虚函数的代码所在的位置。
如果一个虚函数是继承基类并且未做任何更改,那么它的虚函数表的表入口指向父类中该函数的定义,同理,如果改继承的虚函数有了新的定义(实际是实现),那么虚函数表中新定义的虚函数的指针指向新定义函数的代码入口。