Qt5 -- 超好用的“deleteLater()“

C++中如果要在堆内存中创建和销毁对象需要借助关键字new和delete来完成, new操作符用于创建对象, delete操作符用于销毁对象。

虽然随着计算机硬件的不断更新迭代, 内存越来越大, 但是对于一个计算机应用, 系统分配的内存是有限的, 而且”一旦越线“可能就会被系统强行kill, 所以内存还是弥足珍贵的。

合理的利用”delete“可以有效减少应用对内存的消耗。但是delete的不合理使用常常导致应用crash。而”deleteLater()“可以更好的规避风险, 降低崩溃。

一、 ”deleteLater()“的使用

下面是官方文档对 ”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. This does not apply to objects deleted while a previous, nested event loop was still running: the Qt event loop will delete those objects as soon as the new nested event loop starts.
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.
Note: This function is  thread-safe.

总结一下就是以下几点:

  • 首先”deleteLater()“是QObject对象的一个函数, 要想使用此方法, 必须是一个QObject对象。
  • ”deleteLater()“依赖于Qt的event loop机制。
    • 如果在event loop启用前被调用, 那么event loop启用后对象才会被销毁;
    • 如果在event loop结束后被调用, 那么对象不会被销毁;
    • 如果在没有event loop的thread使用, 那么thread结束后销毁对象。
  • 可以多次调用此函数。
  • 线程安全。

二、”deleteLater()“的原理

  • 函数被调用后, 会向QCoreApplication发送一个QDeferredDeleteEvent
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;
}
  • 此对象继续运行, 直到收到QDeferredDeleteEvent
  • 收到QDeferredDeleteEvent后, 此对象将从event loop 中被移除
  • 对象被销毁

三、验证案例

#include 
#include 
#include 
#include 
#include 

QString currentStrTime ()
{
    return QDateTime::currentDateTime().toString("hh:mm:ss");
}

using namespace std;

class DemoClass : public QObject
{
public:
    explicit DemoClass(QString key) : _key(move(key)) {
        qInfo() << currentStrTime() << _key << " => created" ;
        
    }

    ~DemoClass() override {
        qInfo() << currentStrTime() << _key << " => deleted" ;
    }

protected:
    bool event(QEvent* e) override {
        if(e->type() == QEvent::DeferredDelete)
            qInfo() << currentStrTime() << _key << " => receive QDeferredDeleteEvent" ;
        return QObject::event(e);
    }

private:
    QString _key{};
};


int main(int argc, char** argv)
{
    // 创建 beforeEventLoop 对象 并在 EventLoop 开始前调用deleteLater()方法
    auto* beforeEventLoop = new DemoClass("before event loop demo");
    beforeEventLoop->deleteLater();
    // 创建 normalDemo 在运行2秒后调用deleteLater()方法
    auto* normalDemo = new DemoClass("normal demo");
    // 创建 afterEventLoop 在 EventLoop 结束后调用deleteLater()方法
    auto* afterEventLoop = new DemoClass("after event loop demo");
    // 创建QApplication对象
    QApplication a(argc, argv);
    // 销毁 normalDemo 定时器
    QTimer::singleShot(2000, [normalDemo](){
        // 多次调用
        normalDemo->deleteLater();
        normalDemo->deleteLater();
        normalDemo->deleteLater();
    });
    // 系统退出定时器
    QTimer::singleShot(3000, [](){
        QApplication::exit();
    });
    // 启动 EventLoop
    qInfo() << currentStrTime() << "start event loop" ;
    a.exec();
    // 退出 EventLoop
    qInfo() << currentStrTime() << "event loop stoped" ;
    // 调用 afterEventLoop 的 deleteLater()方法
    afterEventLoop->deleteLater();
    return 0;
}

运行结果如下:

"13:51:36" "before event loop demo"  => created
"13:51:36" "normal demo"  => created
"13:51:36" "after event loop demo"  => created
"13:51:36" start event loop
"13:51:36" "before event loop demo"  => receive QDeferredDeleteEvent
"13:51:36" "before event loop demo"  => deleted
"13:51:38" "normal demo"  => receive QDeferredDeleteEvent
"13:51:38" "normal demo"  => deleted
"13:51:39" event loop stoped

在C++中,delete 和 new 必须 配对使用,Qt作为C++的库,显然是不会违背C++原则。但是,qt有自己的内存管理,有时候虽然使用了new,却可以不用使用delete。

Qt半自动的内存管理:

在Qt中,以下情况下你new出的对象你可以不用亲自去delete:

1) QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象。

2) 有些类的对象可以接收设置一些特别的标记,比如:

    QWidget及其派生类的对象,可以设置 Qt::WA_DeleteOnClose 标志位(当close时会析构该对象)

    QAbstractAnimation派生类的对象,可以设置 QAbstractAnimation::DeleteWhenStopped

    QRunnable::setAutoDelete()

    MediaSource::setAutoDelete()

    ...

---------------------------------------------------------------------------------------------------------------

在Qt中,最基础和核心的类是:QObject 。这里只关注两点:
父子关系 和deleteLater

1)父子关系

在Qt中,每个 QObject 内部都有一个list,用来保存所有的 children,还有一个指针,保存自己的parent。当它自己析构时,它会将自己从parent的列表中删除,并且析构掉所有的children。

注意: 这里讲的paent,child与c++的语法无关。这里:如果你在qml文件中写了一个Rectangle,在其内部又加了一个Text,则Text的parent是Rectangle。

建立与解除

创建一个QObject对象时,如果指定了父对象,它就会将自己添加到父对象的 children 列表中:

当一个QObject对象析构时,它会将自己从父对象的 children 列表中移除(parent非0的话):

官方还是建议通过deletelater来进行删除Qobject对象。

void QObject::setParent ( QObject * parent )

通过该函数,将自己从原父对象的children中删除,添加到新parent的children列表中:

获取父对象:QObject * QObject::parent () const

获取子对象,子对象可以有多个:const QObjectList & QObject::children () const

也可以通过findChild来查找某个child。

2)deleteLater

可以下载qt源码,看该函数的实现,我下载了qt5.11的源码:qt-everywhere-src-5.11.1, http://download.qt.io/official_releases/qt/5.11/5.11.1/single/

Qojbect.cpp(文件位置:qt-everywhere-src-5.11.1\qt-everywhere-src-5.11.1\qtbase\src\corelib\kernel )中对该函数的定义:

void QObject::deleteLater()

{
         QCoreApplication::postEvent(this, new QDeferredDeleteEvent());

}

该函数的作用就是发出一个事件,请求自己被干掉。

事件循环稍后收到该事件,会将其派发给这个请求的发出者,然后,事件发出者调用delete将自己干掉:

bool QObject::event(QEvent *e)

{
    switch (e->type()) {
...

    case QEvent::DeferredDelete:

        qDeleteInEventHandler(this);

        break;

}

void qDeleteInEventHandler(QObject *o)

{
    delete o;

}

例子1:

#include

#include

int main(int argc, char *argv[])

{
QApplication app(argc, argv);

QLabel *label = new QLabel("Hello Qt!");

label->show();

return app.exec();

}

这是 C++ GUI Programming with Qt 4 一书的第一个例子。我们注意到这儿的 label 既没有指定parent,也没有对其调用delete。所以,这儿会造成内存泄露。

三种改进方式

1)分配对象到stack而不是heap中

QLabel label("Hello Qt!");

label.show();

2)动手调用delete

int ret = app.exec();

delete label;

return ret;
3)设置标志位,这样,当我们点击关闭按钮时,close()函数将会调用deleteLater

label->setAttribute(Qt::WA_DeleteOnClose);

例子2

#include

#include

int main(int argc, char *argv[])

{
QApplication app(argc, argv);

QLabel label("Hello Qt!");

label.show();

label.setAttribute(Qt::WA_DeleteOnClose);

return app.exec();

}

运行正常,退出时会崩溃 ,因为label被close时,将会 delete 这儿label对象,但label对象却不是通过new分配到heap中的。

为了使得用户减少自己显式使用delete,Qt将delete隐藏的比较深。这样一来,不使用new为对象分配空间时,反倒需要多多小心了。

例子3:这个程序退出时会直接崩溃 。

#include

int main(int argc, char* argv[])

{
   QApplication app(argc, argv);

   QLabel label(tr"Hello Qt!");

   QWidget w;

   label.setParent(&w);

   w.show();

   return app.exec();

}

因为退出时,w 比 label 先被析构,当 w 被析构时,会删除chilren列表中的对象,也就是这儿的 label。但 label 却不是通过new分配在heap中,而是在stack中,delte stack中的东西会导致崩溃。

两种改进办法:

1)将label分配到heap中

   QLabel *label = new QLabel("Hello Qt!");

   label.setParent(&w)

2)确保label先于其parent被析构(调整一下顺序),这样,label析构时将自己从父对象的列表中移除自己,w析构时,children列表中就不会有分配在stack中的对象了。

   QWidget w;

   QLabel label(tr"Hello Qt!");

Qt 对象的父子关系的引入,简化了我们对内存的管理,但是,由于它会在你不太注意的地方调用 delete,所以,需要万分注意。

参考:

https://blog.csdn.net/dbzhang800/article/details/6300025

http://doc.qt.nokia.com/4.7/qobject.html

http://www.cuteqt.com/blog/?p=824
————————————————
版权声明:本文为CSDN博主「小沙与贝壳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35865125/article/details/86531953

使用场景
我们知道,我们可以对某个对象调用deleteLater来延迟删除这个对象,比如我们在QNetworkAccessManager的finished信号中删除QNetworkReply:

    connect(networkManager, &QNetworkAccessManager::finished, [=](QNetworkReply* reply) {
        reply->deleteLater();
        if () {
            qDebug() << reply->header("aa");
            return;
        }
        if () {
            qDebug() << reply->header("aa");
            return;
        }
        ...
        ...
    });

调用deleteLater表示对象将在下一次事件循环处理时被删除,但是在这个函数后面还可以继续使用它。像上面这个例子,有很多退出点,我在每个退出点去delete很麻烦,就可以调用deleteLater。

原理分析
原理是啥呢?我们来看源代码:

void QObject::deleteLater()
{
    QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
}

deleteLater实现很简单,就是向消息队列post了一个QDeferredDeleteEvent消息,接收者是this(很关键)。
我们再继续看这个QDeferredDeleteEvent消息:

QDeferredDeleteEvent::QDeferredDeleteEvent()
    : QEvent(QEvent::DeferredDelete)
    , level(0)
{ }

可以看到消息类型是DeferredDelete,然后我们在QObject找这个类型消息的处理方式:

bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    ...
    case QEvent::DeferredDelete:
        qDeleteInEventHandler(this);
        break;
    ...
}

可以看到这个消息的处理是以this调用qDeleteInEventHandler函数,下面是这个函数的定义:

void qDeleteInEventHandler(QObject *o)
{
    delete o;
}

qDeleteInEventHandler里面就是delete入参传入的指针,那就是相当于delete this。

总结
看到这里小伙伴们可能明白了,关键点是deleteLater中把this作为QDeferredDeleteEvent消息的reciever,那当下一轮事件循环派发消息,reciever的事件处理函数event就会被调用来处理DeferredDelete类型的消息,处理动作就是delete this。
————————————————
版权声明:本文为CSDN博主「一只向前的程序猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42981623/article/details/107804077

你可能感兴趣的:(Qt,qt)