Qt浅谈内存泄露(总结)

来源 http://blog.csdn.net/taiyang1987912/article/details/29271549

一、简介

Qt内存管理机制:Qt 在内部能够维护对象的层次结构。对于可视元素,这种层次结构就是子组件与父组件的关系;对于非可视元素,则是一个对象与另一个对象的从属关系。在 Qt 中,在 Qt 中,删除父对象会将其子对象一起删除。

C++中delete 和 new 必须配对使用(一 一对应):delete少了,则内存泄露,多了麻烦更大。Qt中使用了new却很少delete,因为QObject的类及其继承的类,设置了parent(也可在构造时使用setParent函数或parent的addChild)故parent被delete时,这个parent的相关所有child都会自动delete,不用用户手动处理。但parent是不区分它的child是new出来的还是在栈上分配的。这体现delete的强大,可以释放掉任何的对象,而delete栈上对象就会导致内存出错,这需要了解Qt的半自动的内存管理。另一个问题:child不知道它自己是否被delete掉了,故可能会出现野指针。那就要了解Qt的智能指针QPointer。

二、关联图

(1)Linux内存图,主要了解堆栈上分配内存的不同方式。

(2)在Qt中,最基础和核心的类是:QObject,QObject内部有一个list,会保存children,还有一个指针保存parent,当自己析构时,会自己从parent列表中删除并且析构所有的children。

三、详解

1、Qt的半自动化的内存管理

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

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

(3)QAbstractAnimation派生类的对象,可以设置 QAbstractAnimation::DeleteWhenStopped。

(4)QRunnable::setAutoDelete()、MediaSource::setAutoDelete()。

(5)父子关系:父对象、子对象、父子关系。这是Qt中所特有的,与类的继承关系无关,传递参数是与parent有关(基类、派生类,或父类、子类,这是对于派生体系来说的,与parent无关)。

2、内存问题例子

  • 例子一

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char *argv[])
  4. {
  5. QApplication a(argc, argv);
  6. QLabel *label = new QLabel("Hello Qt!");
  7. label->show();
  8. return a.exec();
  9. }

分析:(1)label 既没有指定parent,也没有对其调用delete,所以会造成内存泄漏。书中的这种小例子也会出现指针内存的问题。
改进方式:(1)分配对象到栈上而不是堆上

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char *argv[])
  4. {
  5. QApplication a(argc, argv);
  6. QLabel label("Hello Qt!");
  7. label.show();
  8. return a.exec();
  9. }

(2)设置标志位,close()后会delete label。

  1. label->setAttribute(Qt::WA_DeleteOnClose);

(3)new后手动delete

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char *argv[])
  4. {
  5. int ret = 0;
  6. QApplication a(argc, argv);
  7. QLabel *label = new QLabel("Hello Qt!");
  8. label->show();
  9. ret = a.exec();
  10. delete label;
  11. return ret;
  12. }
  • 例子二

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char *argv[])
  4. {
  5. QApplication app(argc, argv);
  6. QLabel label("Hello Qt!");
  7. label.show();
  8. label.setAttribute(Qt::WA_DeleteOnClose);
  9. return app.exec();
  10. }

运行:

分析:程序崩溃,因为label被close时,delete &label;但label对象是在栈上分配的内存空间,delete栈上的地址会出错。

有些朋友理解为label被delete两次而错误,可以测试QLabel label("Hello Qt!"); label.show();delete &label;第一次delete就会出错。

  • 例子三

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char* argv[])
  4. {
  5. QApplication app(argc, argv);
  6. QLabel label("Hello Qt!");
  7. QWidget w;
  8. label.setParent(&w);
  9. w.show();
  10. return app.exec();
  11. }

分析:Object内部有一个list,会保存children,还有一个指针保存parent,当自己析构时,会自己从parent列表中删除并且析构所有的children。

w比label先被析构,当w被析构时,会删除chilren列表中的对象label,但label是分配到栈上的,因delete栈上的对象而出错。

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

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char* argv[])
  4. {
  5. QApplication app(argc, argv);
  6. QWidget w;
  7. QLabel label("Hello Qt!");
  8. label.setParent(&w);
  9. w.show();
  10. return app.exec();
  11. }

(2)将label分配到堆上

QLabel *label = new QLabel("Hello Qt!");
label->setParent(&w)

或者QLabel *label = new QLabel("Hello Qt!",this);

  • 例子四:野指针

  1. #include <QApplication>
  2. #include <QLabel>
  3. int main(int argc, char* argv[])
  4. {
  5. QApplication app(argc, argv);
  6. QWidget *w = new QWidget;
  7. QLabel *label = new QLabel("Hello Qt!");
  8. label->setParent(w);
  9. w->show();
  10. delete w;
  11. label->setText("go");     //野指针
  12. return app.exec();
  13. }

(上述程序不显示Label,仅作测试)

分析:程序异常结束,delete w时会delete label,label成为野指针,调用label->setText("go");出错。

改进方式:QPointer智能指针

  1. #include <QApplication>
  2. #include <QLabel>
  3. #include <QPointer>
  4. int main(int argc, char* argv[])
  5. {
  6. QApplication app(argc, argv);
  7. QWidget *w = new QWidget;
  8. QLabel *label = new QLabel("Hello Qt!");
  9. label->setParent(w);
  10. QPointer<QLabel> p = label;
  11. w->show();
  12. delete w;
  13. if (!p.isNull()) {
  14. label->setText("go");
  15. }
  16. return app.exec();
  17. }
  • 例子五:deleteLater

当一个QObject正在接受事件队列时如果中途被你销毁掉了,就是出现问题了,所以QT中建大家不要直接Delete掉一个QObject,如果一定要这样做,要使用QObject的deleteLater()函数,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deletelater也不会有问题。

发送一个删除事件到事件系统:

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

3、智能指针

如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码以释放资源,而智能指针则可以在退出作用域时(不管是正常流程离开或是因异常离开)总调用delete来析构在堆上动态分配的对象。

Qt家族的智能指针:

智能指针

 

引入

QPointer

Qt Object 模型的特性(之一)
注意:析构时不会delete它管理的资源

 

QSharedPointer

带引用计数

Qt4.5

QWeakPointer

 

Qt4.5

QScopedPointer

 

Qt4.6

QScopedArrayPointer

QScopedPointer的派生类

Qt4.6

QSharedDataPointer

用来实现Qt的隐式共享(Implicit Sharing)

Qt4.0

QExplicitlySharedDataPointer

显式共享

Qt4.4

     

std::auto_ptr

   

std::shared_ptr

std::tr1::shared_ptr

C++0x

std::weak_ptr

std::tr1::weak_ptr

C++0x

std::unique_ptr

boost::scoped_ptr

C++0x

(1)QPointer

QPointer是一个模板类。它很类似一个普通的指针,不同之处在于,QPointer 可以监视动态分配空间的对象,并且在对象被 delete 的时候及时更新。

QPointer的现实原理:在QPointer保存了一个QObject的指针,并把这个指针的指针(双指针)交给全局变量管理,而QObject 在销毁时(析构函数,QWidget是通过自己的析构函数的,而不是依赖QObject的)会调用QObjectPrivate::clearGuards 函数来把全局 GuardHash 的那个双指针置为*零,因为是双指针的问题,所以QPointer中指针当然也为零了。用isNull 判断就为空了。

  1. // QPointer 表现类似普通指针
  2. QDate *mydate = new QDate(QDate::currentDate());
  3. QPointer mypointer = mydata;
  4. mydate->year();    // -> 2005
  5. mypointer->year(); // -> 2005
  6. // 当对象 delete 之后,QPointer 会有不同的表现
  7. delete mydate;
  8. if(mydate == NULL)
  9. printf("clean pointer");
  10. else
  11. printf("dangling pointer");
  12. // 输出 dangling pointer
  13. if(mypointer.isNull())
  14. printf("clean pointer");
  15. else
  16. printf("dangling pointer");
  17. // 输出 clean pointer

(2)std::auto_ptr

  1. // QPointer 表现类似普通指针
  2. QDate *mydate = new QDate(QDate::currentDate());
  3. QPointer mypointer = mydata;
  4. mydate->year();    // -> 2005
  5. mypointer->year(); // -> 2005
  6. // 当对象 delete 之后,QPointer 会有不同的表现
  7. delete mydate;
  8. if(mydate == NULL)
  9. printf("clean pointer");
  10. else
  11. printf("dangling pointer");
  12. // 输出 dangling pointer
  13. if(mypointer.isNull())
  14. printf("clean pointer");
  15. else
  16. printf("dangling pointer");
  17. // 输出 clean pointe

auto_ptr被销毁时会自动删除它指向的对象。

std::auto_ptr<QLabel> label(new QLabel("Hello Dbzhang800!"));

(3)其他的类参考相应文档。

4、自动垃圾回收机制

(1)QObjectCleanupHandler

Qt 对象清理器是实现自动垃圾回收的很重要的一部分。QObjectCleanupHandler可以注册很多子对象,并在自己删除的时候自动删除所有子对象。同时,它也可以识别出是否有子对象被删 除,从而将其从它的子对象列表中删除。这个类可以用于不在同一层次中的类的清理操作,例如,当按钮按下时需要关闭很多窗口,由于窗口的 parent 属性不可能设置为别的窗口的 button,此时使用这个类就会相当方便。

  1. #include <QApplication>
  2. #include <QObjectCleanupHandler>
  3. #include <QPushButton>
  4. int main(int argc, char* argv[])
  5. {
  6. QApplication app(argc, argv);
  7. // 创建实例
  8. QObjectCleanupHandler *cleaner = new QObjectCleanupHandler;
  9. // 创建窗口
  10. QPushButton *w = new QPushButton("Remove Me");
  11. w->show();
  12. // 注册第一个按钮
  13. cleaner->add(w);
  14. // 如果第一个按钮点击之后,删除自身
  15. QObject::connect(w, SIGNAL(clicked()), w, SLOT(deleteLater()));
  16. // 创建第二个按钮,注意,这个按钮没有任何动作
  17. w = new QPushButton("Nothing");
  18. cleaner->add(w);
  19. w->show();
  20. // 创建第三个按钮,删除所有
  21. w = new QPushButton("Remove All");
  22. cleaner->add(w);
  23. QObject::connect(w, SIGNAL(clicked()), cleaner, SLOT(deleteLater()));
  24. w->show();
  25. return app.exec();
  26. }

在上面的代码中,创建了三个仅有一个按钮的窗口。第一个按钮点击后,会删除掉自己(通过 deleteLater() 槽),此时,cleaner 会自动将其从自己的列表中清除。第三个按钮点击后会删除 cleaner,这样做会同时删除掉所有未关闭的窗口。

(2)引用计数
  应用计数是最简单的垃圾回收实现:每创建一个对象,计数器加 1,每删除一个则减 1。

  1. class CountedObject : public QObject
  2. {
  3. Q_OBJECT
  4. public:
  5. CountedObject()
  6. {
  7. ctr=0;
  8. }
  9. void attach(QObject *obj)
  10. {
  11. ctr++;
  12. connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(detach()));
  13. }
  14. public slots:
  15. void detach()
  16. {
  17. ctr--;
  18. if(ctr <= 0)
  19. delete this;
  20. }
  21. private:
  22. int ctr;
  23. };

利用Qt的信号槽机制,在对象销毁的时候自动减少计数器的值。但是,我们的实现并不能防止对象创建的时候调用了两次attach()。

(3)记录所有者

更合适的实现是,不仅仅记住有几个对象持有引用,而且要记住是哪些对象。例如:

  1. class CountedObject : public QObject
  2. {
  3. public:
  4. CountedObject() {}
  5. void attach(QObject *obj) {
  6. // 检查所有者
  7. if(obj == 0)
  8. return;
  9. // 检查是否已经添加过
  10. if(owners.contains(obj))
  11. return;
  12. // 注册
  13. owners.append(obj);
  14. connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(detach(QObject*)));
  15. }
  16. public slots:
  17. void detach(QObject *obj) {
  18. // 删除
  19. owners.removeAll(obj);
  20. // 如果最后一个对象也被 delete,删除自身
  21. if(owners.size() == 0)
  22. delete this;
  23. }
  24. private:
  25. QList owners;
  26. ;

现在我们的实现已经可以做到防止一个对象多次调用 attach() 和 detach() 了。然而,还有一个问题是,我们不能保证对象一定会调用 attach() 函数进行注册。毕竟,这不是 C++ 内置机制。有一个解决方案是,重定义 new 运算符(这一实现同样很复杂,不过可以避免出现有对象不调用 attach() 注册的情况)。

四、总结

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

五、参考

Qt浅谈内存泄露(总结)的更多相关文章

  1. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  2. Qt浅谈之总结(整理)

    Qt浅谈之总结(整理) 来源 http://blog.csdn.net/taiyang1987912/article/details/32713781 一.简介 QT的一些知识点总结,方便以后查阅. ...

  3. C学习笔记(11)--- 可变参数,浅谈内存管理 【C基础概念系列完结】

    1.可变参数(variable arguments): 可变参数允许您定义一个函数,能根据具体的需求接受可变数量的参数. int func(int, ... )             (函数 fun ...

  4. JavaScript之浅谈内存空间

    JavaScript之浅谈内存空间 JavaScipt 内存自动回收机制 在JavaScript中,最独特的一个特点就是拥有自动的垃圾回收机制(周期性执行),这也就意味者,前端开发人员能够专注于业余, ...

  5. Qt浅谈之一:内存泄露(总结)

    一.简介       Qt内存管理机制:Qt 在内部能够维护对象的层次结构.对于可视元素,这种层次结构就是子组件与父组件的关系:对于非可视元素,则是一个对象与另一个对象的从属关系.在 Qt 中,在 Q ...

  6. Qt浅谈之一:内存泄露(总结),对于QWidget可以setAttribute(Qt::WA_DeleteOnClose),而且绝对不能手动删除栈上的对象

    一.简介 Qt内存管理机制:Qt 在内部能够维护对象的层次结构.对于可视元素,这种层次结构就是子组件与父组件的关系:对于非可视元素,则是一个对象与另一个对象的从属关系.在 Qt 中,在 Qt 中,删除 ...

  7. cocos2d-x 从onEnter、onExit、 引用计数 谈内存泄露问题

    /////////////////////////////////// //author : zhxfl //date   : 2013.8.29 //email  : 291221622@qq.co ...

  8. Qt——浅谈样式表

    优秀的程序,不仅要有严密逻辑,而且应该有美观的外表.从软件界面,便可看出你是否用心在做,是否是一个有思想的人. Qt样式表的术语和语法规则和HTML CSS有很多相似之处. 样式规则 Qt中样式规则由 ...

  9. Qt浅谈之四十九俄罗斯方块(代码来自网络)

    一.简介 从网上下载了一个Qt实现的俄罗斯方块单机版的源码,觉得非常有意思,故以博客形式记录下来,以便慢慢来研究.在centos6.6下编译运行(注意程序运行需要读取pro目录的配置文件,若把编译目录 ...

随机推荐

  1. 真香!iOS云真机全新上线!

    WeTest 导读 众多开发者已经渐渐适应通过调用线上的安卓真机进行远程调试,但是针对iOS设备,则依然存在“iOS设备昂贵”“无法及时采购iOS最新设备”“无法复现iOS历史系统版本”等问题. 为了 ...

  2. Python 装饰器备忘

    def deco(attr): ''' 装饰器,共包含三层返回结构 \n 第一层:用于接收 @deco 的参数,此处的代码只在初始化装饰器时执行一次 \n 第二层:用于接收 function,此处的代 ...

  3. dbtool一bug跟踪记

    注:这篇日志是好多年前,我还在从兴公司时写的.现在都从从兴公司离职很久了,从兴也没落了,可惜.看了一下,虽然出现了部分代码,但不至于泄漏什么机密,查bug过程的原理也有可以让新手借鉴的地方,就原文照搬 ...

  4. Siki_Unity_2-4_UGUI_Unity5.1 UI 案例学习

    Unity 2-4 UGUI Unity5.1 UI 案例学习 任务1-1:UGUI简介 什么是GUI: 游戏的开始菜单 RPG游戏的菜单栏.侧边栏和功能栏(比如背包系统.任务列表等) 设计用来控制移 ...

  5. 修改Linux系统下的最大文件描述符限制

    通常我们通过终端连接到linux系统后执行ulimit -n 命令可以看到本次登录的session其文件描述符的限制,如下: $ulimit -n1024 当然可以通过ulimit -SHn 1024 ...

  6. Vyatta 网络操作系统

    原文发表于:2010-09-19 转载至cu于:2012-07-21 以下是"开源中国社区"写到的: http://www.oschina.net/news/11423/vyatt ...

  7. DeepLearning - Overview of Sequence model

    I have had a hard time trying to understand recurrent model. Compared to Ng's deep learning course, ...

  8. openresty 安装指南

    对于一些常见的 Linux 发行版本,OpenResty 提供 官方预编译包.确保你首先用这种方式来安装. 如果您还没有下载 OpenResty 的源码包, 请到 Download 页下载. 首先,您 ...

  9. LeetCode 289. Game of Life (C++)

    题目: According to the Wikipedia's article: "The Game of Life, also known simply as Life, is a ce ...

  10. 20181016-4 Alpha阶段第2周/共2周 Scrum立会报告+燃尽图 05

    作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2288 Scrum master:王硕 一.小组介绍 组长:王一可 组员:范 ...