函数原理"/>
C++虚函数原理
一、前言
虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用了子类的成员函数。这种技术可以让父类的指针有“多种形态”。这种类型的识别是在程序运行时定义的,也叫运行时多态。
二、虚函数表实现原理
虚函数的实现是由两个部分组成的,虚函数指针与虚函数表。
2.1 虚函数指针
为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个私有指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。
只有拥有虚函数的类才会拥有虚函数指针,所以拥有虚函数的类的所有对象都会因为虚函数产生额外的开销,并且也会在一定程度上降低程序速度。与JAVA不同,C++将是否使用虚函数这一权利交给了开发者,所以开发者应该谨慎的使用。
2.2 虚函数表
虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地 址,假如子类的虚函数重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的 虚函数的地址;虚函数表指针存在于每个对象中(通常出于效率考虑,会放在对象的开始地址处), 它指向对象所在类的虚函数表的地址;在多继承环境下,会存在多个虚函数表指针,分别指向对应 不同基类的虚函数表。
虚表是属于类的,对于基类与派生类,基类有基类的虚函数表,派生类有派生类的虚函数表。
举个例子,定义三个类A,B,C依次继承
class ClassA
{
public:int m_data1;int m_data2;void func1() {}void func2() {}virtual void vfunc1() {}virtual void vfunc2() {}};class ClassB:public ClassA
{
public:int m_data3;void func2() {} // 非虚函数,基类的同名函数会被隐藏virtual void vfunc1() {}};class ClassC :public ClassB
{
public:int m_data1;int m_data4;void func2() {} // 非虚函数,基类的同名函数会被隐藏virtual void vfunc1() {}};
可以看到,在子类的虚函数表中,基类函数的虚函数表项仍然保留,子类自己的虚函数将跟在表后,如果子类重写了父类的虚函数,虚函数表会更新。比如,classA的虚函数是vFunc1,vFunc2,ClassB继承ClassA,它重写了vFunc2,就会替换该函数地址,以此类推。
三、一些遗留问题
1、 构造函数是否能为虚函数?
不能,
1)如果构造函数是虚函数,那么就需要通过虚表指针vtable 来调用,但此时面对一块没有初始化的内存,到哪里去找 vtable 呢?毕竟,vtable 是在构造函数中才初始化的啊,而不是在其之前。因此构造函数不能为虚函数。
2)构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类 的,因此不存在动态绑定的概念
2、析构函数可以是虚函数吗?
可以,而且最好是虚的,因为此时 vtable 已经初始化了;况且我们通常通过基类的指针来销毁对象,如果析构函数不为虚的话,就不能正确识别对象类型,从而不能正确销毁对象。
3、使用虚函数的时候,子类也要使用virtual关键字吗?
父类使用虚函数是为了让子类重写,那子类重写的时候也需要带virtual关键字吗?比如:
class Base{virtual bool init();
};class Derived : public Base {virtual bool init(); //这里的vitual是必须的吗?好像不用也能编译通过呃…..
};
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类从新声明该虚函数时,可以加,也可以不加,建议父类加virtual,子类加override,这样清晰明了
class Base {virtual bool init();
};class Derived : public Base {bool init() override; // 这样写更清晰
};
4、构造函数中可以调用虚函数吗?
可以,但是没有动态绑定的效果,父类构造函数中调用的仍然是父类版本的函数,子类中调用的仍然是子类版本的函数
5、内联函数、静态成员函数可以是虚函数吗?
内联函数可以,但是不要这么写。内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开,当然你可以写 inline virtual 这样的函数,它也不报错,因为内联是编译器决定的,并不跟是否有关键字inline有关系。而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联;
静态成员函数不可以,因为它是以类为单位的函数,与具体对象无关,无this指针,虚函数是与对象动态绑定的,必定是成员函数,成员函数肯定有this指针,因此是两个冲突的概念;
参考:
C++:构造函数和析构函数能否为虚函数 - 止战 - 博客园
C++虚函数详解_Whitesad_的博客-CSDN博客_c++虚函数
C++ 虚函数、虚函数表剖析_年年年年年的博客-CSDN博客
更多推荐
C++虚函数原理
发布评论