QObject三大核心功能——内存管理
2021-05-30 本文已影响0人
上官宏竹
信号与槽,内存管理,事件处理
QObject的parent or 对象树(Object Tree)
- QObject构造函数中,对于对象树处理的流程:如果指定了parent,那么先判断parent和当前对象是否在同一线程,如果不在就令parent=0,如果当前对象是widget且存在parent(在同一线程),那么赋给parent指针,而且后者添加本对象到子对象的QList,在QWidget构造函数最后发送事件QEvent::ChildAdded。
如果当前对象不是widget就调用setParnet,又调用了QObjectPrivate::setParent_helper。参考 - 对于在局部作用域上创建的父对象及其子对象,要注意对象销毁的顺序,因为父对象销毁时也会销毁子对象,当子对象会在父对象之后被销毁时会引发double free。
- 如果已经指定了非NULL的parent,这时将它设置成了NULL,那么当前实例会从父对象的children中删除,不再受到QObject & parent机制的影响;
- 对于QObject及其派生类,如果彼此之间存在一定联系,则应该尽量指定parent,对于QWidget应该指定parent或者加入布局管理器由管理器自动设置parent。
对象树(Object Tree)
WA_DeleteOnClose
- qwidget关闭的流程(默认是隐藏,而未释放内存),首先用户触发close()槽,然后Qt向widget发送QCloseEvent,默认的QCloseEvent会做如下处理:
将widget隐藏,也就是hide()
;
如果有设置Qt::WA_DeleteOnClose
,那么会接着调用widget的析构函数 - close机制导致的内存泄漏的例子:因为dialog在close时会被隐藏,而且没有设置DeleteOnClose,所以Qt不会去释放dialog,而用户也无法回收dialog的资源。
button.ConnectClicked(func (_ bool) {
dialog := NewMyDialog()
dialog.Exec()
})
解决办法也有三个:
第一种是使用deleteLater,例如:dialog.DeleteLater()
。这会通知Qt的eventloop在下次进入主循环的时候析构dialog,这样一来确实解决了内存泄露,不过缺点是会有不可预测的延迟存在,有时候延迟是难以接受的。
第二种是手动删除widget,适用于parent为NULL的场合:delete dialog;
第三种比较简单,对于单纯显示而不需要和父控件做交互的widget,直接设置DeleteOnClose
即可,close时widget会被自动析构。
QObject::deleteLater()
当一个QObject正在接受事件队列时如果中途被你delete掉了,就是出现问题了,所以QT中建议大家不要直接delete掉一个QObject,如果一定要这样做,要使用QObject的deleteLater()
函数,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deleteLater()
也不会有问题。
deleteLater源码如下,从源码可以看到,返回到事件循环后,调用deleteLater()的对象才会被销毁,否则不执行。如果线程中没有运行着的事件循环,线程中的对象调用了deleteLater(),当线程结束后对象才被销毁。当第一次QDeferredDeleteEvent传递给事件循环后,对象的任何待处理事件(pending events)都从事件队列中移除。
void QObject::deleteLater()
{
QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
}
bool QObject::event(QEvent *e)
{
switch (e->type()) {
......
case QEvent::DeferredDelete:
qDeleteInEventHandler(this);
break;
}
}
void qDeleteInEventHandler(QObject *o)
{
delete o;
}