Qt 中 deleteLater() 函数的使用

有时候写代码时总是忘记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()就会显得很得体。

1、deleteLater的描述

Qt帮助文档里面对它的描述如下:
在这里插入图片描述

也就是说,我们调用这个函数,并不会直接进行delete,而是向事件循环发送了一个delete事件,也就说当控件返回到事件循环时,这个对象才会被删除。

并且多次调用这个函数是安全的;当传递第一个延迟删除事件时,对象的任何挂起事件都将从事件队列中删除。

2、deleteLater的原理

首先我们沿着这个调用进入到该函数中:

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显示之后会被析构。

  • 16
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值