关于Qt内存释放机制的简单讲解

很多C/C++初学者常犯的一个错误就是,使用malloc、new分配了一块内存却忘记释放,导致内存泄漏。Qt的对象模型提供了一种Qt对象之间的父 子关系,当很多个对象都按一定次序建立起来这种父子关系的时候,就组织成了一颗树。当delete一个父对象的时候,Qt的对象模型机制保证了会自动的把 它的所有子对象,以及孙对象,等等,全部delete,从而保证不会有内存泄漏的情况发生。

任何事情都有正反两面作用,这种机制看上去挺好,但是却会对很多Qt的初学者造成困扰,我经常给别人回答的问题是:1,new了一个Qt对象之后,在什么 情况下应该delete它?2,Qt的析构函数是不是有bug?3,为什么正常delete一个Qt对象却会产生segment fault?等等诸如此类的问题,这篇文章就是针对这个问题的详细解释。

在每一个Qt对象中,都有一个链表,这个链表保存有它所有子对象的指针。当创建一个新的Qt对象的时候,如果把另外一个Qt对象指定为这个对象的父对象, 那么父对象就会在它的子对象链表中加入这个子对象的指针。另外,对于任意一个Qt对象而言,在其生命周期的任何时候,都还可以通过setParent函数 重新设置它的父对象。当一个父对象在被delete的时候,它会自动的把它所有的子对象全部delete。当一个子对象在delete的时候,会把它自己 从它的父对象的子对象链表中删除。

QWidget是所有在屏幕上显示出来的界面对象的基类,它扩展了Qt对象的父子关系。一个Widget对象也就自然的成为其父Widget对象的子 Widget,并且显示在它的父Widget的坐标系统中。例如,一个对话框(dialog)上的按钮(button)应该是这个对话框的子 Widget。

关于Qt对象的new和delete,下面我们举例说明。

例如,下面这一段代码是正确的:

int main()
{
QObject* objParent = new QObject(NULL);
QObject* objChild = new QObject(objParent);
QObject* objChild2 = new QObject(objParent);
delete objParent;
}

在上述代码片段中,objParent是objChild的父对象,在objParent对象中有一个子对象链表,这个链表中保存它所有子对象的指针,在 这里,就是保存了objChild和objChild2的指针。在代码的结束部分,就只有delete了一个对象objParent,在objParent对象的析构函数会遍历它的子对象链表,并且把它所有的子对象(objChild和objChild2)一一删除。所以上面这段代码是安 全的,不会造成内存泄漏。

如果我们把上面这段代码改成这样,也是正确的:

int main()

{

QObject* objParent = new QObject(NULL);

QObject* objChild = new QObject(objParent);

QObject* objChild2 = new QObject(objParent);

delete objChild;

delete objParent;

}

在这段代码中,我们就只看一下和上一段代码不一样的地方,就是在delete objParent对象之前,先delete objChild对象。在delete objChild对象的时候,objChild对象会自动的把自己从objParent对象的子对象链表中删除,也就是说,在objChild对象被 delete完成之后,objParent对象就只有一个子对象(objChild2)了。然后在delete objParent对象的时候,会自动把objChild2对象也delete。所以,这段代码也是安全的。

Qt的这种设计对某些调试工具来说却是不友好的,比如valgrind。比如上面这段代码,valgrind工具在分析代码的时候,就会认为objChild2对象没有被正确的delete,从而会报告说,这段代码存在内存泄漏。哈哈,我们知道,这个报告是不对的。

我们在看一看这一段代码:

int main()

{

QWidget window;

QPushButton quit("Exit", &window);

}

在这段代码中,我们创建了两个widget对象,第一个是window,第二个是quit,他们都是Qt对象,因为QPushButton是从QWidget派生出来的,而QWidget是从QObject派生出来的。这两个对象之间的关系是,window对象是quit对象的父对象,由于他们 都会被分配在栈(stack)上面,那么quit对象是不是会被析构两次呢?我们知道,在一个函数体内部声明的变量,在这个函数退出的时候就会被析构,那 么在这段代码中,window和quit两个对象在函数退出的时候析构函数都会被调用。那么,假设,如果是window的析构函数先被调用的话,它就会去 delete quit对象;然后quit的析构函数再次被调用,程序就出错了。事实情况不是这样的,C++标准规定,本地对象的析构函数的调用顺序与他们的构造顺序相 反。那么在这段代码中,这就是quit对象的析构函数一定会比window对象的析构函数先被调用,所以,在window对象析构的时候,quit对象已 经不存在了,不会被析构两次。

如果我们把代码改成这个样子,就会出错了,对照前面的解释,请你自己来分析一下吧。

int main()

{

QPushButton quit("Exit");

QWidget window;

quit.setParent(&window);

}

但是我们自己在写程序的时候,也必须重点注意一项,千万不要delete子对象两次,就像前面这段代码那样,程序肯定就crash了。

最后,让我们来结合Qt source code,来看看这parent/child关系是如何实现的。

在本专栏文章的第一部分“对象数据存储”,我们说到过,所有Qt对象的私有数据成员的基类是QObjectData类,这个类的定义如下:

typedef QList<QObject*> QObjectList;

class QObjectData

{

public:

QObject *parent;

QObjectList children;

// 忽略其它成员定义

};

我们可以看到,在这里定义了指向parent的指针,和保存子对象的列表。其实,把一个对象设置成另一个对象的父对象,无非就是在操作这两个数据。把子对 象中的这个parent变量设置为指向其父对象;而在父对象的children列表中加入子对象的指针。当然,我这里说的非常简单,在实际的代码中复杂的 多,包含有很多条件判断,有兴趣的朋友可以自己去读一下Qt的源代码。

Qt的内存释放策略(内存自动释放机制)相关推荐

  1. 第六讲:Obj-C 内存管理4 - 自动释放池

    转:http://tigercat1977.blog.163.com/blog/static/2141561122012111294616203/ 第六讲:Obj-C 内存管理4 - 自动释放池 主要 ...

  2. iOS内存管理 —— 自动释放池和runloop

    iOS内存管理 -- 自动释放池和runloop 1. 自动释放池 1.1 自动释放池介绍 1.2 自动释放池底层原理 objc_autoreleasePoolPush autoreleaseNoPa ...

  3. 32位linux 内存占用,LINUX内存高,触发OOM-KILLER问题解决

    最近遇到两起Linux的内存问题,其一是触发了oom-killer导致系统挂 1. 首先确认该系统的版本是32位 #uname -aLinux alarm 2.6.9-67.ELsmp #1 SMP  ...

  4. Rust是如何实现内存安全的--理解RAII/所有权机制/智能指针/引用

    不带自动内存回收(Garbage Collection)的内存安全是Rust语言最重要的创新,是它与其他语言最主要的区别所在,是Rust语言设计的核心. Rust希望通过语言的机制和编译器的功能,把程 ...

  5. 的clear会清空内存吗_Python内存分配时有哪些不为你知的小秘密?

    Python 中的sys模块极为基础而重要,它主要提供了一些给解释器使用(或由它维护)的变量,以及一些与解释器强交互的函数. 本次的Python学习教程分享将会频繁地使用该模块的getsizeof() ...

  6. JS的堆内存和栈内存

    概念     堆和栈都是运行时内存中分配的一个数据区,因此也被称为堆区和栈区,但二者存储的数据类型和处理速度不同. 堆(heap)     队列优先,先进先出.用于复杂数据类型(引用类型)分配空间,例 ...

  7. qt 如何 指针 自动 释放内存_要是面试官再问你智能指针的问题,就拿这篇文章“盘他”!!!...

    前一段时间,有不少朋友问我关于智能指针的问题,并且反映经常会在面试中被面试官问到,所以今天小豆君就来讲讲我对智能指针的理解,希望能对大家有所帮助 既然讲智能指针,我们就先来看看它为什么会出现. 1 传 ...

  8. redis怎么不让存byte_Redis 删除数据后不能自动释放内存么?

    前言 上周刚来了个应届小师弟,组长说让我带着,周二问了我这样一个问题:师兄啊,我用top命令看了下服务器的内存占用情况,发现Redis内存占用严重,于是我就删除了大部分不用的keys,为什么内存占用还 ...

  9. RAMMAP(运行内存清理工具)自动释放内存,并利用pyqt5制作图形界面

    上一篇文章用python制作了一个自动清理内存的程序,利用cmd端口调用Rammap,不过只是做了一个托盘图标,这回用pyqt5做一个简单的图形界面,并实现对自动清理模式的一些设置 首先利用pyqt5 ...

  10. 内存管理-定时器循环、内存布局、tagged pointer、weak指针、copy、自动释放池

    先上代码,我们平时用的定时器,cadisplaylink.nstimer,CADisplayLink.NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用 ...

最新文章

  1. 用tcpdump查看端口包
  2. 最小生成树(kruskal+prime)
  3. 美化“关于本机”中Logo、机型、CPU等信息详细教程
  4. 临床预测模型开发checklist详解
  5. Java——递归遍历目录【获取指定目录下的文件名称】
  6. apache 一个站点配置多个域名
  7. C语言中sizeof和strlen的区别是什么
  8. 由硬盘供电不稳、数据线品质差造成的蓝屏
  9. 第六章 图 学习小结
  10. 【OpenCV】OpenCV函数精讲之 -- 初级图像混合
  11. Twemproxy测试Redis分片主从架构
  12. java flink项目_IDEA上运行Flink任务的实战教程
  13. C++变量初始化问题
  14. 数据库SQL(介绍)
  15. 微信公众号 | 适合程序员的公众号排版
  16. SQL分组统计-每个学科前三名
  17. 毕业一年小结——说好的战斗呢?
  18. 交换机:广播风暴产生原因与解决方法、STP生成树协议 ,根桥(根交换机)、备份根桥、非根交换机、根端口、指定端口、非根非指定端口、桥ID
  19. 安卓眼球追踪_iPhone 11 Pro 可配合 Eyeware Beam 眼球追踪玩 PC 大屏游戏
  20. windows server 2003 工具资源命令集

热门文章

  1. Linux增加四个用户分别并命名,16、Linux用户和组管理命令演练和实战应用,,150R...
  2. django.core.exceptions.ImproperlyConfigured: The included URLconf
  3. 近视眼手术之后计算机专业,白领人士做完近视眼手术后,可以正常用电脑工作吗?...
  4. hp台式计算机生产日期,如何查询hp笔记本生产日期
  5. python 标准库 书籍_《Python标准库》Python语言的权威之作
  6. [译]用C#创建一个屏幕保护程序
  7. Fixturlaser对中仪维修GO/NXA Pro系列
  8. 比较容易考前计算机好的985大学,容易考的985有哪些?附性价比高的985大学排名...
  9. Microsoft word “很抱歉,无法打开xxx.docx,因为内容有问题” 的解决办法
  10. 抓包工具whistle安装与使用