写在前面的总结:
建议:对于不能指定父对象的对象(对象通过moveToThread()移入其他线程、没有继承QObject的类产生的对象),在其他线程通过deleteLater()内存回收,其他通过指定父对象进行内存回收
对于Qt的内存回收机制
1、手动删除
1.1 对于new出来的对象,如果对象调用deleteLater()而不是使用delete,只有当控制器返回事件循环,才会析构对象,deleteLater() 函数可以用于析构不在本线程中的对象;
1.2 对于new出来的对象,如果直接 delete 对象,内存能立马被回收
2、Qt半自动回收
Qobject的子类,如果创建对象的时候指定父对象,当父对象被delete,会先把子对象销毁
void QObject::deleteLater()
Schedules this object for deletion.
The object will be deleted when control returns to the event loop. If the event loop is not running when this function is called (e.g. deleteLater() is called on an object before QCoreApplication::exec()), the object will be deleted once the event loop is started. If deleteLater() is called after the main event loop has stopped, the object will not be deleted. Since Qt 4.8, if deleteLater() is called on an object that lives in a thread with no running event loop, the object will be destroyed when the thread finishes.
Note that entering and leaving a new event loop (e.g., by opening a modal dialog) will not perform the deferred deletion; for the object to be deleted, the control must return to the event loop from which deleteLater() was called.
Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.
翻译:
当控制权返回事件循环,调用deleteLater()的对象才会被销毁(猜测:比如在QCoreApplication::exec()这个主事件循环内new了一个对象,这个对象调用了deleteLater(),但是只有在QCoreApplication::exec()返回结果,也就是QCoreApplication::exec()这个函数执行完后才会销毁)。
如果调用此函数的时候事件循环没有运行(比如在调用QCoreApplication::exec()前一个对象的 deleteLater() 函数被调用),一旦事件循环开始,对象就被销毁。如果在主事件循环停止后调用deleteLater(),对象不会被销毁。Qt 4.8 以后,如果线程中没有运行着的事件循环,线程中的对象调用deleteLater(),当线程结束后对象被销毁。
注意:进入和离开一个新的事件循环不会执行延迟删除操作(比如打开一个模式对话框);控制权必须从调用deleteLater()所在位置返回到事件循环,对象才会被销毁。
进入 dispatch_next_event() 表示处理事件,退出事件循环,从 dispatch_next_event() 返回表示返回事件循环
while (is_active)
{
while (!event_queue_is_empty)
{
dispatch_next_event();
} wait_for_more_events();
}
什么是事件循环
1、事件循环一般用exec()函数开启。QApplicaion::exec()、QMessageBox::exec()都是事件循环。其中前者又被称为主事件循环。
事件循环首先是一个无限“循环”,程序在exec()里面无限循环,能让跟在exec()后面的代码得不到运行机会,直至程序从exec()跳出。从exec()跳出时,事件循环即被终止。QEventLoop::quit()能够终止事件循环。
其次,之所以被称为“事件”循环,是因为它能接收事件,并处理之。当事件太多而不能马上处理完的时候,待处理事件被放在一个“队列”里,称为“事件循环队列”。当事件循环处理完一个事件后,就从“事件循环队列”中取出下一个事件处理之。当事件循环队列为空的时候,它和一个啥事也不做的永真循环有点类似,但是和永真循环不同的是,事件循环不会大量占用CPU资源。
事件循环的本质就是以队列的方式再次分配线程时间片。
2、事件循环是可以嵌套的,一层套一层,子层的事件循环执行exec()的时候,父层事件循环就处于中断状态;当子层事件循环跳出exec()后,父层事件循环才能继续循环下去。
Qt半自动内存回收
在每一个Qt对象(也就是Qobject的子类)中,都有一个链表,这个链表保存有它所有子对象的指针。当创建一个新的Qt对象的时候,如果把另外一个Qt对象指定为这个对象的父对象, 那么父对象就会在它的子对象链表中加入这个子对象的指针。另外,对于任意一个Qt对象而言,在其生命周期的任何时候,都还可以通过setParent函数 重新设置它的父对象。当一个父对象在被delete的时候,它会自动的把它所有的子对象全部delete。当一个子对象在delete的时候,会把它自己 从它的父对象的子对象链表中删除。
QWidget是所有在屏幕上显示出来的界面对象的基类,它扩展了Qt对象的父子关系。一个Widget对象也就自然的成为其父Widget对象的子 Widget,并且显示在它的父Widget的坐标系统中。例如,一个对话框(dialog)上的按钮(button)应该是这个对话框的子 Widget。
关于Qt对象的new和delete,下面我们举例说明。
例如,下面这一段代码是正确的:
int main()
{
QObject* objParent = new QObject(NULL);
QObject* objChild = new QObject(objParent);
QObject* objChild2 = new QObject(objParent);
delete objParent;
}
我们用一张图来描述这三个对象之间的关系:
在上述代码片段中,objParent是objChild的父对象,在objParent对象中有一个子对象链表,这个链表中保存它所有子对象的指针,在这里,就是保存了objChild和objChild2的指针。在代码的结束部分,就只有delete了一个对象objParent,在 objParent对象的析构函数会遍历它的子对象链表,并且把它所有的子对象(objChild和objChild2)一一删除。所以上面这段代码是安 全的,不会造成内存泄漏。
如果我们把上面这段代码改成这样,也是正确的:
int main()
{
QObject* objParent = new QObject(NULL);
QObject* objChild = new QObject(objParent);
QObject* objChild2 = new QObject(objParent);
delete objChild;
delete objParent;
}
在这段代码中,我们就只看一下和上一段代码不一样的地方,就是在delete objParent对象之前,先delete objChild对象。在delete objChild对象的时候,objChild对象会自动的把自己从objParent对象的子对象链表中删除,也就是说,在objChild对象被 delete完成之后,objParent对象就只有一个子对象(objChild2)了。然后在delete objParent对象的时候,会自动把objChild2对象也delete。所以,这段代码也是安全的。
Qt的这种设计对某些调试工具来说却是不友好的,比如valgrind。比如上面这段代码,valgrind工具在分析代码的时候,就会认为objChild2对象没有被正确的delete,从而会报告说,这段代码存在内存泄漏。哈哈,我们知道,这个报告是不对的。
我们在看一看这一段代码:
int main()
{
QWidget window;
QPushButton quit("Exit", &window);
}
在这段代码中,我们创建了两个widget对象,第一个是window,第二个是quit,他们都是Qt对象,因为QPushButton是从 QWidget派生出来的,而QWidget是从QObject派生出来的。这两个对象之间的关系是,window对象是quit对象的父对象,由于他们 都会被分配在栈(stack)上面,那么quit对象是不是会被析构两次呢?我们知道,在一个函数体内部声明的变量,在这个函数退出的时候就会被析构,那 么在这段代码中,window和quit两个对象在函数退出的时候析构函数都会被调用。那么,假设,如果是window的析构函数先被调用的话,它就会去 delete quit对象;然后quit的析构函数再次被调用,程序就出错了。事实情况不是这样的,C++标准规定,本地对象的析构函数的调用顺序与他们的构造顺序相 反。那么在这段代码中,这就是quit对象的析构函数一定会比window对象的析构函数先被调用,所以,在window对象析构的时候,quit对象已 经不存在了,不会被析构两次。
如果我们把代码改成这个样子,就会出错了,对照前面的解释,请你自己来分析一下吧。
int main()
{
QPushButton quit("Exit");
QWidget window;
quit.setParent(&window);
}
但是我们自己在写程序的时候,也必须重点注意一项,千万不要delete子对象两次,就像前面这段代码那样,程序肯定就crash了。
最后,我们来看看如何实现父子关系:
1、使用 setParent() 指定父对象
2、new 一个对象的时候指定父对象。new 出来的对象的父类如果是窗口类型的类,构造函数 parent 的类型为 QWidget,否则为 QObject