C++虚函数探索笔记(3)——延伸思考:虚函数应用的一些其他情形
关注问题:
虚函数的作用
虚函数的实现原理
虚函数表在对象布局里的位置
虚函数的类的sizeof
纯虚函数的作用
多级继承时的虚函数表内容
虚函数如何执行父类代码
多继承时的虚函数表定位,以及对象布局
虚析构函数的作用
虚函数在QT中的应用
虚函数与inline修饰符,static修饰符
虚析构函数
大家都知道,在C++里需要自己严格管理好资源的分配和回收。通常情况下,
在一个对象被析构的时候,是要由其释放其申请到的各种资源的。最常见的,当
然就是内存资源啦。
当只有一个类的时候,我们可以不用考虑太多,只要在析构函数里检查并释
放所有申请到的资源即可。但是在这个类继承了一个抽象接口基类时,就有点点
不一样了。让我们看看类的析构过程:
在大多数的类的使用时,通常都是直接删除该类的实例对象,然后该类的析
构函数就会被调用,从而使得这个类在析构函数里执行的资源释放代码被执行到
。
如果这个类继承了其他类,那么编译器还会在这个类的析构函数里自动添加
对父类的析构函数的调用,从而将父类里申请的资源也进行释放。如果偶多个父
类,也会依次调用各个析构函数。
倘若继承的是一个抽象接口类,并且在程序运行期,可能通过一个基类指针
将此对象释放掉,那么致命而又隐藏的内存泄露BUG就出现啦……因为试图删除的
是基对象,删除时调用的是基类的析构函数,而基类的析构函数当然是不会去调
用子类的析构函数的罗!
让我们看看下面的代码,使用vs2008编译并运行的时候,将会在程序运行结
束时报告内存泄漏情况(如果要在linux下编译测试,需要去掉第一行的include
,以及return前的_CrtDumpMemoryLeaks()函数,然后使用linux下检查内存泄
露的工具进行测试)。
//Source filename: Win32Con.cpp
#include
class parent
{
public:
parent() { }
/*virtual */ ~parent() { }
};
class child:public parent
{
public:
child()
{
p=new char[1000];
}
~child()
{
delete[] p;
}
char *p;
};
void free_child(parent *pp)
{
delete pp;
}
int main()
{
child *obj=new child();
free_child(obj);
_CrtDumpMemoryLeaks();
return 0;
}
在这段代码里我们创建的是一个child类型的对象,然后使用free_child
(parent*)函数来试图释放这个对象,这个时候,只会调用到parent::
~parent()这个析构函数,而不会调用到child::~child()!
如何解决这个问题呢?
很简单的,只要在parent::~parent()前增加 virtual关键字,将其变成
一个虚函数。这样,无论是以这个对象的父类指针进行删除的时候,就会从虚函
数表里定位到子类child的析构函数,这样就能够从子类开始一级一级的向上调用
析构函数,从而正确的将这个对象在各个继承层次上申请的所有资源都释放掉。
正因为这个原因,在很多C++编程原则的文章或者书里都会提到这样的原则:
如果一个类要被设计为可被继承的基类,那么其析构函数应该被声明为虚函
数。
虚函数在QT中的应用
在QT里虚函数的应用非常的广泛,事实上,在大多数的C++类库里都不可避免
的要使用到虚函数。这里简单的列举QT里使用虚函数的情况:
QT的事件机制
是使用了虚函数的,你因此才可以自定义事件处理函数。比如最核心的
QObject类的定义里(在qobject.h里),我们可以看到如下的虚函数定义:
virtual bool event(QEvent *);
virtual bool eventFilter(QObject *, QEvent *);
然后,在QWidget类继承QObject类后重新实现了上面的两个虚函数,完成很
多窗口控件类的缺省事件处理。
当你要编写自定义的QT控件的时候,对event虚函数的重新实现就更是重要啦
。
QT的信号和槽
QT的槽函数可以被声明为虚函数,所以虽然QT在实现信号和槽机制的时候可
能出于效率或者运行代价的原因未采用虚函数机制,但是我们依然可以在必要的
时候使用虚函数来完成一些特定功能。比如为一些自定义控件类抽象出来一个抽
象接口基类,在做信号和槽的连接的时候是对基类指针进行操作,而在基类里的
槽定义为虚函数,那么虚函数在此依然可以实现信号与槽的多态。
然而虚函数在调用的时候,一定要经历查表的步骤,是存在一定的运行开销
的,对于一些非常频繁的槽调用还是应该考虑到使用虚函数产生的代价的。
其他
在虚函数上,static和inline这两个关键词与virtual显得很不友好。
从语义上即可看出,static和virtual完全就是冲突的,所以如果你试图为一
个虚函数增加一个static限定词,那么你的C++编译器就会很负责任的报告一个严
重错误给你。
而inline的含义和虚函数其实也是非常冲突的,但是inline在语法上只是给
编译器一个建议,而不是强制的语义限定,所以C++编译器应该会忽略掉inline关
键词,继续正常的编译。