有时候写代码时总是忘记delete一些自己new的对象,每次都是在整个类写完之后对整个类的资源做回收。也是相当于检查一遍,避免一些未被处理资源。
而这两天在检查的时候,发现了一个比较好的写法,obj->deleteLater()
;这种写法,按照字面意思理解,就是对象延迟析构。
go语言中有一个延迟执行的语句,非常好用,比如:
filePath := "e:/code/golang.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println("文件打开失败", err)
}
defer file.Close() //及时关闭file句柄
。。。
上面的defer file.Close()
语句的作用就是延迟关闭文件,所以在这行代码的下面对该文件进行操作是没有问题的,知道操作完成之后,才会关闭文件句柄。
那么,Qt 中的 deleteLater()
函数是不是对应的也是这样的设计。
想了想,其实这个方法是很方便的一种,并且在某些场景下必须使用这种方法才能保证程序的健壮运行。否则会出现一些异常,比如在弹出的菜单中进行父窗口的析构等等。。。
为什么会报错?
因为Qt是事件驱动的,当QObject正在接收事件队列时被销毁掉会出错。
void MyClass::on_btnConnectClicked()
{
auto btn = new QPushButton(this);
btn->deleterLater();
if(XXX)
{
//btn to do something...
return;
}
if(YYY)
{
//btn to do something...
return;
}
}
比如像上面这种有多个退出点的程序,如果要delete则会有多处,代码会比较臃肿。使用deleteLater()
就会显得很得体。
也就是说,我们调用这个函数,并不会直接进行delete,而是向事件循环发送了一个delete事件,也就说当控件返回到事件循环时,这个对象才会被删除。
并且多次调用这个函数是安全的;当传递第一个延迟删除事件时,对象的任何挂起事件都将从事件队列中删除。
首先我们沿着这个调用进入到该函数中:
void QObject::deleteLater()
{
QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
}
通过上面的函数我们可以看到,该函数像该控件发送了一个类型为QEvent::DeferredDelete
的事件。并且接受者也是自己。
这样我们就能找到对于该事件进行的操作:
bool QObject::event(QEvent *e)
{
switch (e->type()) {
....
case QEvent::DeferredDelete:
qDeleteInEventHandler(this);
break;
....
}
}
void qDeleteInEventHandler(QObject *o)
{
delete o;
}
上面的代码可以看到,接收到这个事件的操作也只是delete了对象。
那么会不会对QDeferredDeleteEvent
这个东西好奇呢?我们也找到这个类的定义:
class Q_CORE_EXPORT QDeferredDeleteEvent : public QEvent
{
public:
explicit QDeferredDeleteEvent();
~QDeferredDeleteEvent();
int loopLevel() const { return level; }
private:
int level;
friend class QCoreApplication;
};
定义很简单,只有一个成员变量 level。并且它的初始循环等级为 0.
QDeferredDeleteEvent::QDeferredDeleteEvent()
: QEvent(QEvent::DeferredDelete)
, level(0)
{ }
下面我们看下是怎样进行postEvent的。
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
...
if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
// remember the current running eventloop for DeferredDelete
// events posted in the receiver's thread.
// Events sent by non-Qt event handlers (such as glib) may not
// have the scopeLevel set correctly. The scope level makes sure that
// code like this:
// foo->deleteLater();
// qApp->processEvents(); // without passing QEvent::DeferredDelete
// will not cause "foo" to be deleted before returning to the event loop.
// If the scope level is 0 while loopLevel != 0, we are called from a
// non-conformant code path, and our best guess is that the scope level
// should be 1. (Loop level 0 is special: it means that no event loops
// are running.)
int loopLevel = data->loopLevel;
int scopeLevel = data->scopeLevel;
if (scopeLevel == 0 && loopLevel != 0)
scopeLevel = 1;
static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
}
...
}
通过上面的函数我们可以看到,在这边会将该事件的循环等级,也就是loopLevel 重新进行设置。
下面的函数我们上次已经讲过了,函数的作用是立即对前面已经通过 QCoreApplication::postEvent()
排队等待的所有事件立即进行派发。如果不了解的话,可以看看前面的文章–《Qt自定义事件的实现》
void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type, QThreadData *data)
{
...
if (pe.event->type() == QEvent::DeferredDelete) {
// DeferredDelete events are sent either
// 1) when the event loop that posted the event has returned; or
// 2) if explicitly requested (with QEvent::DeferredDelete) for
// events posted by the current event loop; or
// 3) if the event was posted before the outermost event loop.
int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();
int loopLevel = data->loopLevel + data->scopeLevel;
const bool allowDeferredDelete =
(eventLevel > loopLevel
|| (!eventLevel && loopLevel > 0)
|| (event_type == QEvent::DeferredDelete
&& eventLevel == loopLevel));
if (!allowDeferredDelete) {
// cannot send deferred delete
if (!event_type && !receiver) {
// we must copy it first; we want to re-post the event
// with the event pointer intact, but we can't delay
// nulling the event ptr until after re-posting, as
// addEvent may invalidate pe.
QPostEvent pe_copy = pe;
// null out the event so if sendPostedEvents recurses, it
// will ignore this one, as it's been re-posted.
const_cast<QPostEvent &>(pe).event = 0;
// re-post the copied event so it isn't lost
data->postEventList.addEvent(pe_copy);
}
continue;
}
}
...
}
其实,在上面的这个函数里面,是有一个while循环的,循环条件是i < data->postEventList.size()
i 是这个事件队列的开始偏移量。
如果遇到了类型为QEvent::DeferredDelete
的事件,程序会先进行判断,该事件是不是需要发出去,如果暂时不能发出去 ,则会将该事件重新post到postEvent事件队列里面,进入下一次循环。
如果可以发,则会直接发出该事件。
这个方法在父类和子类的关系的引入,会存在一些比较不太注意的地方调用delete,所以这个方法在这些时候会显得比较实用。比如:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLabel* label = new QLabel("test");
//label->deleteLater();
label->show();
return a.exec();
}
如果我们注释掉上面的deleteLater,则该函数会出现内存泄漏,如果我们去掉注释,代码能够被正常相应,并且label显示之后会被析构。