目录
一、什么是对象树
Qt中的 QObject 会用对象树来组织管理自己,那什么是对象树?
这个概念非常好理解。因为 QObject 类就有一个私有变量 QList<QObject *>,专门存储这个类的子孙后代们。比如创建一个 QObject 并指定父对象时,就会把自己加入到父对象的childre()列表中,也就是 QList<QObject *> 变量中。
注意:这个父子关系不是指类中继承的父子关系,这一点需要重点强调以下。QT里面的这个父子关系和类中的语法没有半毛钱的关系。
举个例子:就是一个我们讲一个对话框Dialog,其中有一个button按钮,它属于对话框的一个组成部分。那我们就把这个按钮称为它的一个子对象。这样的一个对话框呢,就叫做这个子对象的父对象,其中实际的操作就是按钮设置parent为对话框Dialog,对话框就是作为button的父对象。
二、对象树的优点
好处就是:当父对象被析构时子对象也会被析构。
从一定程度上讲,简化了内存回收机制。举个例子,有一个窗口 Window,里面有 Label标签、TextEdit 文本输入框、Button 按钮这三个元素,并且都设置 Window 为它们的父对象。这时候我做了一个关闭窗口的操作,作为程序员的你是不是自然想到将所有和窗口相关的对象析构啊?古老的办法就是一个个手动 delete 呗。是不是很麻烦?Qt 运用对象树模式,当父对象被析构时,子对象自动就delete 掉了,不用再写一大堆的代码了。
所以,在我点击该运行程序之后后,系统会根据这个树状的结构,释放掉整个结构的内存。
三、对象树潜在的隐患
任何事物都有两面性,Qt中的对象树机制也是一样,它有好处当然也会存在相应的隐患。
隐患就是:会导致双重释放。
那是什么意思呢,就是说,在Qt中,如果一个QObject对象同时被其对象树和程序中的其他部分(如栈或堆上的直接管理)所持有,那么当对象树被销毁时,该对象可能会被自动删除。如果之后程序中的其他部分又尝试删除该对象,就会导致双重释放,进而可能引发程序崩溃。
举一个例子:
看下面一段代码,在 main.cpp 文件中创造一个QPushButton类,同时创造了一个Widget窗口,btn 指定 w 父对象。
如果不仔细查看,代码没有什么问题。但其实程序已经出现崩溃。原因是:在main 函数中,变量存储在栈中,栈的特点为 后进先出,也就是说,w 先出栈,btn后出栈。由于对象树的机制,btn 指定 w 作为 父对象,它会在 w 析构之前被析构一次,而 w 出栈又被析构一次,这就导致了双重释放,程序会崩溃。
崩溃结果为:
同时正确写法应该为:创建 btn 应该在 w 的后面,也就会先释放btn,后释放 w,因为这样对象树内部同样还有一个机制,如果它查询到了 btn 已经被释放了,那么它就不会再继续释放 btn 内存了。