关键词:
当我们在使用Qt时不可避免得需要接触到内存的分配和使用,即使是在使用Python,Golang这种带有自动垃圾回收器(GC)的语言时我们仍然需要对Qt的内存管理机制有所了解,以更加清楚的认识Qt对象的生命周期并在适当的时机加以控制或者避免进入陷阱。
这篇文章里我们将学习QObject & parent对象管理机制,以及QWidget与内存管理这两点Qt的基础知识。
QObject和内存管理
在Qt中,我们可以大致把对象分为两类,一类是QObject
和它的派生类;另一类则是普通的C++类。
对于第二种对象,它的生命周期与管理和普通的C++类基本没有区别,而QObject
和它的派生类则有以下的显著区别:
QObject
和其派生类可以使用SIGNAL/SLOT机制- 它们一般会有一个
parent
父对象的指针,用于内存管理(后面重点说明) - 对于
QWidget
和其派生类来说,内存管理要稍微复杂一些,因为QWidget
需要和eventloop高度配合才能工作(后面也会重点说明)
signal和slot一般来说并不会对内存管理产生影响,但是对close()
槽的处理会对QWidget
产生一些影响,所以我们放在后面讲解。
那么先来看一下QObject和parent机制。
QObject的parent
我们时常能看到QWidget或者其他的控件的构造函数中有一项参数parent
,默认值都为NULL,例如:
QLineEdit(const QString &contents, QWidget *parent = nullptr);
QWidget(QWidget *parent = nullptr, Qt::WindowFlags f = ...);
这个parent
的作用就在于使当前的对象实例加入parent指定的QObject及其派生类的children中,当一个QObject被delete或者调用了它的析构函数时,所有加入的children也会全部被析构。
如果parent
设置为NULL,会有如下的情况:
- 如果是构造时直接指定了NULL,那么当前实例不会有父对象存在,Qt也不能自动析构该实例除非实例超出作用域导致析构函数被调用,或者用户在恰当的实际使用
delete
操作符或者使用deleteLater
方法; - 如果已经指定了非NULL的
parent
,这时将它设置成了NULL,那么当前实例会从父对象的children中删除,不再受到QObject & parent机制的影响; - 对于
QWidget
,parent
为NULL时代表其为一个顶层窗口,也可以就是独立于其他widget在系统任务栏单独出现的widget,对于永远都是顶层窗口的widget,例如QDialog
,当parent
不为NULL时他会显示在父widget中心区域的上层; - 如果
QWidget
的parent
为NULL或是其他值,在其加入布局管理器或者QMainWindow
设置widget时,会自动将parent
设置为相应的父widget,在父控件销毁时这些子控件以及布局管理器对象会一并销毁。
所以我们可以看出,QObject对象实际上拥有一颗类实例关系树,在树中保存了所有通过指定parent
注册的子对象,而子对象里又保存有其子对象的关系树,所以当一个父对象被销毁时,所有依赖或间接依赖于它的对象都会被正确的释放,使用者无需手动管理这些资源的释放操作。
基于此原理,我们可以放心的让Qt管理资源,这里有几个建议:
- 对于QObject及其派生类,如果彼此之间存在一定联系,则应该尽量指定parent,对于
QWidget
应该指定parent或者加入布局管理器由管理器自动设置parent。 - 对象只需要在局部作用域存在时可以选择不进行内存分配,利用局部作用域变量的生命周期自动清理资源。
- 对于非
QWidget
的对象来说,如果不指定非NULLparent
,则需要自己管理对象资源。QWidget
比较特殊,我们在下一节讲解。 - 对于在局部作用域上创建的父对象及其子对象,要注意对象销毁的顺序,因为父对象销毁时也会销毁子对象,当子对象会在父对象之后被销毁时会引发double free。
QWidget和内存的释放
QWidget
也是QObject
的子类,所以在parent机制上是没有区别的,然而实际使用时我们更多的是使用“关闭”(close)而不是delete去删除控件,所以差异就出现了。
先提一下widget关闭的流程,首先用户触发close()
槽,然后Qt向widget发送QCloseEvent
,默认的QCloseEvent
会做如下处理:
- 将widget隐藏,也就是
hide()
; - 如果有设置
Qt::WA_DeleteOnClose
,那么会接着调用widget的析构函数
我们可以看到,widget的关闭实际是将其隐藏,而没有释放内存,虽然我们有时会重写closeEvent
但也不会手动释放widget。
看一个因为close机制导致的内存泄漏的例子,我们在button被单击后弹出某个自定义对话框:
button.ConnectClicked(func (_ bool)
dialog := NewMyDialog()
dialog.Exec()
)
因为dialog在close时会被隐藏,而且没有设置DeleteOnClose
,所以Qt不会去释放dialog,而用户也无法回收dialog的资源,也行你会说golang的GC不是能处理这种情况吗,然而遗憾的是GC并不能处理cgo分配的资源,所以如果你期望GC做善后的话恐怕要失望了,每次点击按钮后内存用量都会增加一点,没错,内存泄露了。
那么给dialog设置一个parent,像这样,会如何呢?
dialog.SetParent(self)
遗憾的是,并没有什么区别,因为这样只是把dialog加入父控件的children,并没有删除dialog,只有父对象被销毁时内存才会真正释放。
解决办法也有三个。
第一种是使用deleteLater
,例如:
dialog.DeleteLater()
这会通知Qt的eventloop在下次进入主循环的时候析构dialog,这样一来确实解决了内存泄露,不过缺点是会有不可预测的延迟存在,有时候延迟是难以接受的。
第二种是手动删除widget,适用于parent为NULL的场合:
C++:
delete dialog;
golang:
dialog.DestroyMyDialog()
说明一下,DestroyType
也是qtmoc生产的帮助函数,因为golang没有析构函数的概念,所以goqt使用生成的该帮助函数显示调用底层C++对象的析构函数。
第三种比较简单,对于单纯显示而不需要和父控件做交互的widget,直接设置DeleteOnClose
即可,close时widget会被自动析构。
当然对于PyQt5来说并不会存在如上的问题,sip库能很好的与python的GC一起工作。唯一需要注意的是有时底层C++对象已经被释放,但是上层python对象依然存在,这时使用该对象将导致抛错。
总结
Qt提供了一套方便的机制帮助我们进行内存和资源管理,使我们从繁重的劳动中得到了部分的解放,但同时也要注意到那些很容易坑,这样才能写出健壮的正确执行的程序。
如有错误之处,欢迎批评指正。
参考:
http://doc.qt.io/qt-5/qwidget.html
http://doc.qt.io/qt-5/qobject.html
http://doc.qt.io/qt-5/objecttrees.html
https://stackoverflow.com/questions/20164015/is-deletelater-necessary-in-pyqt-pyside
python的内存管理机制(代码片段)
...三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制一、对象的引用计数机制Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。引用计数增加的情况:1,一个对象分配一个新名称2,将... 查看详情
mysql内存管理机制浅析(代码片段)
...权不得随意使用,转载请联系小编并注明来源。MySQL内存管理机制浅析一、placementnew的定义二、placementnew使用场景三、placementnew和MySQL内存管理机制的关系四、MySQL中mem_root使用场景一、placementnew的定义通常情况下,C+... 查看详情
详解redis内存管理机制和实现(代码片段)
原文:详解Redis内存管理机制和实现Redis是一个基于内存的键值数据库,其内存管理是非常重要的。本文内存管理的内容包括:过期键的懒性删除和过期删除以及内存溢出控制策略。最大内存限制Redis使用maxmemory参数限制最大可用... 查看详情
python内存管理(代码片段)
Date:2019-05-27Author:Sun内存管理机制?python中万物皆对象,python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它?Python的内存管理机制:引入计数、垃圾回收、内存池机制一、变量与对象1、变量... 查看详情
java内存管理与反射机制(代码片段)
Java内存管理理解Java程序运行时的内存管理,对很多相关技术的学习都有帮助,如Java的反射机制。Java是纯面对对象语言,没有C/C++“全局变量”的概念,只有“成员变量”与“局部变量”的概念,所有的... 查看详情
python的内存管理机制(代码片段)
引入计数在Python中,每个对象都有指向该对象的引用总数---引用计数查看对象的引用计数:sys.getrefcount()importsysalist=[\'a\',\'b\',\'c\']>>>sys.getrefcount(alist)2>>>b=alist>>>sys.getrefcount(alist)3【引用计数增加】 查看详情
qt进程间通信及内存共享,信号量,锁机制,线程同步(代码片段)
....com/discuss/389380?type=1来源:牛客网 Qt进程间通信及内存共享,信号量,锁机制,线程同步APP内打开030分享1、进程与线程的基础知识2、qt进程通信的共享内存概念:共享内存指(sharedmemo 查看详情
javascript内存管理(代码片段)
...会中断整个代码执行,释放不可能再被使用的变量,释放内存,这个工作机制是周期性的,我们会在下文详细探讨。 可释放对象functionfn1()varobj1=name:\'xiaomuchen\',age:\'20\'functionfn2()varobj2=name 查看详情
[新星计划]python内存管理|引用计数垃圾回收内存池机制(代码片段)
...贝系列文章https://blog.csdn.net/cpen_web/category_11089219.htmlPython内存管理三大块○引用计数○垃圾回收○内存池Python的内存管理以引用计数为主,垃圾回收为辅,还有个内存池Python动态类型○对象是储存在内存中的实体○我们在... 查看详情
垃圾收集机制与内存分配策略(代码片段)
Java语言与其他编程语言有一个非常突出的特点,自动化内存管理机制。而这种机制离不开高效率的垃圾收集器(GarbageCollection)与合理的内存分配策略,这也是本篇文章将要描述的两个核心点。引一句周志明老师对Java中的内存... 查看详情
javascript中的垃圾收集机制(代码片段)
...,也就是说,执行环境会负责管理代码执行过程中使用的内存。 在编写JavaScript程序时,开发人员不用再关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。 这种垃圾收集机制的原理其实很简... 查看详情
学习012垃圾回收机制算法分析(代码片段)
...著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的... 查看详情
从内存管理原理,窥探os内存管理机制(代码片段)
摘要:本文将从最简单的内存管理原理说起,带大家一起窥探OS的内存管理机制,由此熟悉底层的内存管理机制,写出高效的应用程序。本文分享自华为云社区《探索OS的内存管理原理》,作者:元闰子。... 查看详情
c#垃圾回收机制(gc)的概述资源清理内存管理(代码片段)
...产生很多的数据比如:intstring变量,这些数据都存储在内存里,如果不合理的管理他们,就会内存溢出导致程序崩溃C#内置了自动垃圾回收GC,在编写代码时可以不需要担心内存溢出的问题变量失去引用后GC会帮我们... 查看详情
c#垃圾回收机制(gc)的概述资源清理内存管理(代码片段)
...产生很多的数据比如:intstring变量,这些数据都存储在内存里,如果不合理的管理他们,就会内存溢出导致程序崩溃C#内置了自动垃圾回收GC,在编写代码时可以不需要担心内存溢出的问题变量失去引用后GC会帮我们... 查看详情
回收机制gc(代码片段)
...源操作,通常简单分为以下几个步骤:为对应的资源分配内存→初始化内存→使用资源 →清理资源 →释放内存。 2、应用程序对资源(内存使用)管理的方式,常见的一般有如下几种: [1]手动管理:C,C++... 查看详情
内存管理(代码片段)
前言像C语言这样的底层语言一般都有底层的内存管理接口,比如malloc()和free()用于分配和释放内存。而对于JavaScript来说,会在创建变量时分配内存,并且在不再使用它们时自动释放内存,这个自动释放内存的过程称为垃圾回收... 查看详情
十分良心!全网最详细的java自动内存管理机制及性能优化教程(代码片段)
... 一、运行时数据区域首先来看看Java虚拟机所管理的内存包括哪些区域,就像我们要了解一个房子,我们得先知道这个房子大体构造。根据《Java虚拟机规范(JavaSE7版)》的规定,请看下图: Java虚拟机运行时数据区 1.... 查看详情