zoukankan      html  css  js  c++  java
  • Qt 半内存管理

    在C++中学习过程中,我们都知道: delete 和 new 必须配对使用(一 一对应):delete少了,则内存泄露,多了麻烦更大。 Qt作为C++的库,显然是不会违背C++的前述原则的。可是: 在Qt中,我们很多时候都疯狂地用new,却很少用delete,缺少的 delete 去哪儿了?!


    注:本文暂不涉及智能指针(smart pointer)相关的东西,你可以考虑 Qt 智能指针学习 一文 Qt半自动的内存管理

    在Qt中,以下情况下你new出的对象你可以不用亲自去delete (但你应该清楚delete在何处被Qt调用的,怎么被调用的):

    QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象(本文内容围绕这一点展开 )

    除此之外,有些类的对象可以接收设置一些特别的标记,比如:

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

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

    QAbstractAnimation::DeleteWhenStopped

    QRunnable::setAutoDelete()

    MediaSource::setAutoDelete() ... 注意:这些用法会有些陷阱 ,请注意看本文最后的3个小例子。 在Qt中,最基础和核心的类是:QObject 。它的魔力很大,本文只关注两点: 父子关系 deleteLater

    父子关系

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

    注意:在 Qt 中,我们经常会遇到基类、派生类,或父类、子类。

    这是对于派生体系来说的,和在C++相关书中看到的完全一样,与这的parent无关父对象、子对象、父子关系。 这是Qt中所特有的,也就是这儿的parent所引入的,与类的继承关系无关 建立与解除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Q_INVOKABLE QObject::QObject ( QObject * parent = 0 )
    //创建一个QObject对象时,如果指定了父对象,它就会将自己添加到父对象的 children 列表中
    QObject::~QObject () [virtual]
    //当一个QObject对象析构时,它会将自己从父对象的 children 列表中移除(parent非0的话)
    void QObject::setParent ( QObject * parent )
    //通过该函数,将自己从原父对象的children中删除,添加到新parent的children列表中
    //注:这三个函数都是通过一个内部私有函数来实现的,这就是
    QObjectPrivate::setParent_helper(QObject *o)
    //获取父、子对象
     
    //每个QObject只有一个父对象:
    QObject * QObject::parent () const
    //子对象可以有多个
    const QObjectList & QObject::children () const
    //所以可以根据条件来查找喽:
    T QObject::findChild ( const QString & name = QString() ) const
    QList<T> QObject::findChildren ( const QString & name = QString() ) const

    deleteLater

    deleteLater 包含两层意思了 delete later 呵呵,似乎这是废话哈。 删除自己

    在去年春节前的时候吧,有人对

    obj-> deleteLater()

    会像下面一样调用delete:

    delete obj;

    感到不解。然后我写了这样一个C++例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class A
    {
      public:
      A(){}
      void deleteMe()
      {
          delete this;
      }
    };
     
    int main()
    {
      A * a = new A;
      a->deleteMe();
      return 0;
    }

    应该不需要解释吧 later

    Qt 是事件驱动的,所以发送一个删除事件到事件系统就可以啦:

    1
    2
    3
    4
    void QObject::deleteLater()
    {
        QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));
    }

    事件循环稍后看到该事件就会将其派发会这个widget:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    bool QObject::event(QEvent *e)
    {
        switch (e->type())
        {
          case QEvent::DeferredDelete:
             ...
             break;
        }
    }

    一些例子 无关痛痒? 很简短、很熟悉的一个例子是不?但是 如果你发现对象的析构函数始终不被成功调用 ,会有什么感觉?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <QApplication>
    #include <QLabel>
    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。 所以,这儿会造成内存泄露。 书中解释说,对于这种小例子,这点内存泄露不算什么。不清楚官方这个例子的意图是什么,或许是一开始就让大家用指针吧。 三种改进方式 分配对象到stack而不是heap中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    QLabel label("Hello Qt!");
    label.show();
     
    //设置标志位,这样,当我们点击关闭按钮时,close()函数将会调用deleteLater
    label->setAttribute(Qt::WA_DeleteOnClose);
    //动手调用delete(不就是少了一个么,我们补上还不行么)
    int ret = app.exec();
    delete label;
    return ret;

    单独列一个吧 强化一下对前一个例子的了解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <QApplication>
    #include <QLabel>
    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为对象分配空间时,反倒需要多多小心了。 隐蔽很深?

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <QtGui>
    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中,可想而知,delete 一个再stack中的对象会怎么样了。相当于

    1
    2
    QLabel label();
    delete &label;

    两种改进办法: 一是,将label分配到heap中

    1
    2
    QLabel *label = new QLabel("Hello Qt!");
    label.setParent(&w)

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

    1
    2
    QWidget w;
    QLabel label(tr"Hello Qt!");

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

  • 相关阅读:
    第三章 第六节 SWT类的常量与函数
    第四章 第一节 概述
    第四章 第四节 使用RowLayout
    第四章 第三节 使用FillLayout
    IIS7下使用4.0框架集成模式URLRewriter重写中文URL乱码问题
    Google Chrome下无法获取标签innerHTML问题
    IIS7.5应用程序池集成模式和经典模式的区别
    IIS使用4.0框架时Request.RawUrl获取问题
    解决iOS与pad里ifram无法滑动问题
    react实现双向绑定
  • 原文地址:https://www.cnblogs.com/laien/p/5642752.html
Copyright © 2011-2022 走看看