c++ 虚函数的几个知识点
虚函数的一些问题
构造函数可以设置为虚的吗?
答:不能。因为虚函数的调用是需要通过“虚函数表”来进行的,而虚函数表也需要在对象实例化之后才能够进行调用。在构造对象的过程中,还没有为“虚函数表”分配内存。所以,这个调用也是违背先实例化后调用的准则。
子类的默认构造函数总要执行的操作:执行基类的代码后调用父类的构造函数。
c++虚析构函数
如果类是父类,则必须声明为虚析构函数。基类声明一个虚析构函数,为了确保释放派生对象时,按照正确的顺序调用析构函数。
如果析构函数不是虚的,那么编译器只会调用对应指针类型的虚构函数。切记,是指针类型的,不是指针指向类型的!而其他类的析构函数就不会被调用。例如如下代码:
Employee* pe = new Singer;
delete pe;
只会调用Employee的析构函数而不会调用Singer类的析构函数。如果这个类不是父类也可以定义虚析构函数,只是效率方面问题罢了
那些函数不能是虚函数?
除了上面说的构造和析构函数往外。
①友元函数不是虚函数,因为友元函数不是类成员,只有类成员才能使虚函数。
②静态成员函数不能是虚。在C++中,静态成员函数不能被声明为virtual函数。首先会编译失败,也就是不能同过编译。
原因如下:
static成员不属于任何类对象或类实例,所以即使给此函数加上virtual也是没有任何意义的。
静态与非静态成员函数之间有一个主要的区别。那就是静态成员函数没有隐藏的this指针。对于虚函数,它的调用恰恰需要this指针。在有虚函数的类实例中,this指针调用vptr指针,vptr找到vtable(虚函数列表),通过虚函数列表找到需要调用的虚函数的地址。总体来说虚函数的调用关系是:this指针->vptr->vtable ->virtual虚函数。所以说,static静态函数没有this指针,也就无法找到虚函数了
③内联函数也不能是虚的,因为要在编译的时候展开,而虚函数要求动态绑定。另外就是虚函数的类对象必须包含vptr,但是内联函数是没有地址的,编译的时候直接展开了所以不行。
④构造函数也不行。
⑤成员函数模板不能是虚函数。因为c++ 编译器在解析一个类的时候就要确定虚函数表的大小,如果允许一个虚函数是模板函数,那么compiler就需要在parse这个类之前扫描所有的代码,找出这个模板成员函数的调用(实例化),然后才能确定vtable的大小,而显然这是不可行的,除非改变当前compiler的工作机制。因为类模板中的成员函数在调用的时候才会创建
虚函数表是共享还是独有的?
答:虚函数表是针对类的,一个类的所有对象的虚函数表都一样。在gcc编译器的实现中虚函数表vtable存放在可执行文件的只读数据段.rodata中。是编译器在编译器为我们处理好的。
虚函数表和虚函数指针的位置?
答:既不在堆上,也不在栈上。虚函数表(vtable)的表项在编译期已经确定,也就是一组常量函数指针。跟代码一样,在程序编译好的时候就保存在可执行文件里面。程序运行前直接加载到内存中即可。而堆和栈都是在运行时分配的。而跟虚函数表对应的,是虚函数表指针(vptr),作为对象的一个(隐藏的)成员,总是跟对象的其他成员一起。如果对象分配在堆上,vptr也跟着在堆上;如果对象分配在栈上,vptr也在栈上……
编译器如何处理虚函数表
对于派生类来说,编译器简历虚表的过程有三步:
拷贝基类的虚函数表,如果是多继承,就拷贝每个基类的虚函数表
查看派生类中是否有重写基类的虚函数,如果有,就替换成已经重写后的虚函数地址
查看派生类中是否有新添加的虚函数,如果有,就加入到自身的虚函数表中
构造函数或析构函数中调用虚函数会怎样?
首先不应该在构造函数和析构函数中调用虚函数。
在构造函数中调用虚函数。假如有一个动物基类,这个基类定义了一个虚函数来表示动物的行为,叫做action。我们在基类的构造函数中调用这个虚函数。然后有一个派生类重写了该虚函数。当我们创建一个派生类对象的时候,首先会执行基类部分,因此执行基类的构造函数,然后才会执行子类的构造函数。编译器在执行基类构造函数中的虚函数时,会认为这是一个基类的对象,因为派生类还并没有构造出来。因此达不到动态绑定的效果,父类构造函数中调用的仍然是父类版本的函数,子类中调用的仍然是子类版本的函数
在析构函数中调用虚函数。析构函数也是一样,派生类先进行析构,如果有父类的析构函数中有virtual函数的话,派生类的内容已经被析构了,C++会视其基类,执行基类的virtual函数。