Inside Qt Series (十二):Qt对象之间的父子关系

来源:互联网 发布:淘宝店铺的经营范围 编辑:程序博客网 时间:2024/03/29 23:29

很多C/C++初学者常犯的一个错误就是,使用malloc、new分配了一块内存却忘记释放,导致内存泄漏。Qt的对象模型提供了一种Qt对象之间的父子关系,当很多个对象都按一定次序建立起来这种父子关系的时候,就组织成了一颗树。当delete一个父对象的时候,Qt的对象模型机制保证了会自动的把它的所有子对象,以及孙对象,等等,全部delete,从而保证不会有内存泄漏的情况发生。

任何事情都有正反两面作用,这种机制看上去挺好,但是却会对很多Qt的初学者造成困扰,我经常给别人回答的问题是:1,new了一个Qt对象之后,在什么情况下应该delete它?2,Qt的析构函数是不是有bug?3,为什么正常delete一个Qt对象却会产生segmentfault?等等诸如此类的问题,这篇文章就是针对这个问题的详细解释。

在每一个Qt对象中,都有一个链表,这个链表保存有它所有子对象的指针。当创建一个新的Qt对象的时候,如果把另外一个Qt对象指定为这个对象的父对象,那么父对象就会在它的子对象链表中加入这个子对象的指针。另外,对于任意一个Qt对象而言,在其生命周期的任何时候,都还可以通过setParent函数重新设置它的父对象。当一个父对象在被delete的时候,它会自动的把它所有的子对象全部delete。当一个子对象在delete的时候,会把它自己从它的父对象的子对象链表中删除。

QWidget是所有在屏幕上显示出来的界面对象的基类,它扩展了Qt对象的父子关系。一个Widget对象也就自然的成为其父Widget对象的子Widget,并且显示在它的父Widget的坐标系统中。例如,一个对话框(dialog)上的按钮(button)应该是这个对话框的子Widget。

关于Qt对象的new和delete,下面我们举例说明。

例如,下面这一段代码是正确的:

  1. int main()
  2. {
  3.   QObject* objParent = new QObject(NULL);
  4.   QObject* objChild = new QObject(objParent);
  5.   QObject* objChild2 = new QObject(objParent);
  6.   delete objParent;
  7. }
复制代码

我们用一张图来描述这三个对象之间的关系:

 

如果我们把上面这段代码改成这样,也是正确的:

  1. int main()
  2. {
  3.   QObject* objParent = new QObject(NULL);
  4.   QObject* objChild = new QObject(objParent);
  5.   QObject* objChild2 = new QObject(objParent);
  6.   delete objChild;
  7.   delete objParent;
  8. }
复制代码

在这段代码中,我们就只看一下和上一段代码不一样的地方,就是在delete objParent对象之前,先deleteobjChild对象。在deleteobjChild对象的时候,objChild对象会自动的把自己从objParent对象的子对象链表中删除,也就是说,在objChild对象被delete完成之后,objParent对象就只有一个子对象(objChild2)了。然后在deleteobjParent对象的时候,会自动把objChild2对象也delete。所以,这段代码也是安全的。

Qt的这种设计对某些调试工具来说却是不友好的,比如valgrind。比如上面这段代码,valgrind工具在分析代码的时候,就会认为objChild2对象没有被正确的delete,从而会报告说,这段代码存在内存泄漏。哈哈,我们知道,这个报告是不对的。

我们在看一看这一段代码:

  1. int main()
  2. {
  3.   QWidget window;
  4.   QPushButton quit("Exit", &window);
  5. }
复制代码

在这段代码中,我们创建了两个widget对象,第一个是window,第二个是quit,他们都是Qt对象,因为QPushButton是从QWidget派生出来的,而QWidget是从QObject派生出来的。这两个对象之间的关系是,window对象是quit对象的父对象,由于他们都会被分配在栈(stack)上面,那么quit对象是不是会被析构两次呢?我们知道,在一个函数体内部声明的变量,在这个函数退出的时候就会被析构,那么在这段代码中,window和quit两个对象在函数退出的时候析构函数都会被调用。那么,假设,如果是window的析构函数先被调用的话,它就会去deletequit对象;然后quit的析构函数再次被调用,程序就出错了。事实情况不是这样的,C++标准规定,本地对象的析构函数的调用顺序与他们的构造顺序相反。那么在这段代码中,这就是quit对象的析构函数一定会比window对象的析构函数先被调用,所以,在window对象析构的时候,quit对象已经不存在了,不会被析构两次。

如果我们把代码改成这个样子,就会出错了,对照前面的解释,请你自己来分析一下吧。

  1. int main()
  2. {
  3.   QPushButton quit("Exit");
  4.   QWidget window;
  5.   quit.setParent(&window);
  6. }
复制代码

但是我们自己在写程序的时候,也必须重点注意一项,千万不要delete子对象两次,就像前面这段代码那样,程序肯定就crash了。

 

 

====================================
声明:
《Inside Qt Series》专栏文章是Qt核心技术论坛(InsideQt.com)原创技术文章。
本系列专栏文章可随意转载,但必须保留本段声明和每一篇文章的原始地址。
作者保留版权,未经作者同意,不得用于任何商业用途

《Inside Qt Series》专栏文章总索引:
http://www.insideqt.com/bbs/viewthread.php?tid=9
本文原始地址:
http://www.insideqt.com/bbs/viewthread.php?tid=72

前一篇:emit,幕后的故事
http://www.insideqt.com/bbs/viewthread.php?tid=55
后一篇:Qt/e体系结构概述
http://www.insideqt.com/bbs/viewthread.php?tid=113
====================================