rambo

C++面试语法糖(一) 虚函数深入

一、C++析构函数为什么要为虚函数?

1.为什么基类的析构函数是虚函数?

部分转载自http://www.cnblogs.com/zhice163/archive/2012/07/10/2584835.html

在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。

xigou

基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针p的过程是:先释放继承类的资源,再释放基类资源。构造函数执行的顺序是从基类到派生类。

xigou1

基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针p的过程是:只是释放了基类的资源,而没有调用继承类的析构函数。调用dosomething()函数执行的也是基类定义的函数。

问题来了!!这样的删除只能够删除基类对象,而不能删除子类对象,造成内存泄漏。在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数。析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。

xigou2

基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针p的过程是:只是释放了继承类的资源,再调用基类的析构函数。调用dosomething()函数执行的也是继承类定义的函数。

重点总结:如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销。当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,

只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。像其他虚函数一样,析构函数的虚函数性质都将继承。

二、C++继承类this指针

xigou3

问题:派生类初始化的时候调用基类的构造函数的时候,基类构造函数中的this指针,为什么this指针指向的是派生类的对象,却不能调用派生类的对象b?

回答:基类指针在程序运行的时候的确指向的是一个派生类的对象,但指针的类型仍然是基类指针。C++是一种强类型语言,因此不能用基类指针类型的指针直接调用派生类;而且,同一个类可能有多种不同的派生类,因此不知道实际指向的会是哪个派生类。如果确信是某个派生类的话,可以用这样的方法来调用:

注意如果在构造函数中的话,ClxDerived的部分现在还是没有初始化的,因此即使读出b的数据,也不会是2。

三、构造函数和赋值操作符不是虚函数

复制构造函数、赋值操作符和析构函数总称为复制控制。

1、构造函数不能是虚函数!

1)存储角度:如果构造函数是虚的,就需要通过 virtual table的指针来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。(当一系列构造函数调用正发生时,每个构造函数都已经设置vptr指向它自己的 vtable。如果函数调用使用虚机制,它将只产生通过它自己的vtable的调用,而不是最后的vtable(所有构造函数被 调用后才会有最后的vtable)。

2)使用角度:虚函数的作用在于通过基类的指针或者引用来调用它的时候能够变成调用派生类的那个成员函数构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数

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

2、赋值操作符不应是虚函数。

虽然可以在基类中将成员函数operator=定义成虚函数,但这样做并不会影响派生类中赋值操作符的使用。因为每个类有自己的赋值操作符。每个类的赋值操作符都有一个和类本身类型相同的形参该类型必须不同于继承层次中任意其他类的赋值操作符的形参类型。因此子类的赋值操作符和基类的赋值操作符并不是同一个。但是,在这个子类中仍然有基类的那个操作符,但不是赋值操作符。

将赋值操作符设为虚函数容易让人混淆,因为虚函数必须在基类和派生类中具有相同的形参,基类赋值操作符有一个形参是自身类类型的引用,如果该操作符为虚函数,则每个类都将得到一个虚函数成员,该成员定义了参数为一个基类对象的operator=。