Qt的delete释放内存详解 deleteLater

0 625
索鸟 2021-03-24
需要:0索币

在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所引入的,与类的继承关系无关

建立与解除

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++例子:


class A

{

  public:

  A(){}

  void deleteMe()

  {

      delete this;

  }

};


int main()

{

  A * a = new A;

  a->deleteMe();

  return 0;

应该不需要解释吧


later

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


void QObject::deleteLater()

{

    QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));

}

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


bool QObject::event(QEvent *e)

{

    switch (e->type()) {

...

    case QEvent::DeferredDelete:

         ...

一些例子

无关痛痒?

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


#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中

QLabel label("Hello Qt!");

label.show();

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

label->setAttribute(Qt::WA_DeleteOnClose);

动手调用delete(不就是少了一个么,我们补上还不行么)

int ret = app.exec();

delete label;

return ret;

单独列一个吧

强化一下对前一个例子的了解


#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为对象分配空间时,反倒需要多多小心了。


隐蔽很深?

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


#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中的对象会怎么样了。相当于

QLabel label();

delete &label;

两种改进办法:

一是,将label分配到heap中

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

   label.setParent(&w)

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

   QWidget w;

   QLabel label(tr"Hello Qt!");

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


参考

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


http://www.cuteqt.com/blog/?p=824

————————————————

版权声明:本文为CSDN博主「dbzhang800」的原创文章

原文链接:https://blog.csdn.net/dbzhang800/article/details/6300025

回帖
  • 消灭零回复
相关主题
2020年最新最新Kubernetes视频教程(K8s)教程 2
程序员转型之制作网课变现,月入过万告别996 1
索鸟快传2.0发布啦 1
两个不同网络的电脑怎么实现文件的互相访问呢? 1
网盘多账号登录软件 1
Java实战闲云旅游项目基于vue+element-ui 1
单点登录技术解决方案基于OAuth2.0的网关鉴权RSA算法生成令牌 1
QT5获取剪贴板上文本信息QT设置剪贴板内容 1
springboot2实战在线购物系统电商系统 1
python web实战之爱家租房项目 1
windows COM实用入门教程 1
C++游戏开发之C++实现的水果忍者游戏 1
计算机视觉库opencv教程 1
node.js实战图书管理系统express框架实现 1
C++实战教程之远程桌面远程控制实战 1
相关主题
PHP7报A non well formed numeric value encountered 0
Linux系统下关闭mongodb的几种命令分享 0
mongodb删除数据、删除集合、删除数据库的命令 0
Git&Github极速入门与攻坚实战课程 0
python爬虫教程使用Django和scrapy实现 0
libnetsnmpmibs.so.31: cannot open shared object file 0
数据结构和算法视频教程 0
redis的hash结构怎么删除数据呢? 0
C++和LUA解析器的数据交互实战视频 0
mongodb errmsg" : "too many users are authenticated 0
C++基础入门视频教程 0
用30个小时精通C++视频教程可能吗? 0
C++分布式多线程游戏服务器开发视频教程socket tcp boost库 0
C++培训教程就业班教程 0
layui的util工具格式时间戳为字符串 0
C++实战教程之远程桌面远程控制实战 1
网络安全培训视频教程 0
LINUX_C++软件工程师视频教程高级项目实战 0
C++高级数据结构与算法视频教程 0
跨域问题很头疼?通过配置nginx轻松解决ajax跨域问题 0