qt的内存管理机制(代码片段)

apocelipes apocelipes     2023-01-22     540

关键词:

当我们在使用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机制的影响;
  • 对于QWidgetparent为NULL时代表其为一个顶层窗口,也可以就是独立于其他widget在系统任务栏单独出现的widget,对于永远都是顶层窗口的widget,例如QDialog,当parent不为NULL时他会显示在父widget中心区域的上层;
  • 如果QWidgetparent为NULL或是其他值,在其加入布局管理器或者QMainWindow设置widget时,会自动将parent设置为相应的父widget,在父控件销毁时这些子控件以及布局管理器对象会一并销毁。

所以我们可以看出,QObject对象实际上拥有一颗类实例关系树,在树中保存了所有通过指定parent注册的子对象,而子对象里又保存有其子对象的关系树,所以当一个父对象被销毁时,所有依赖或间接依赖于它的对象都会被正确的释放,使用者无需手动管理这些资源的释放操作。

基于此原理,我们可以放心的让Qt管理资源,这里有几个建议:

  1. 对于QObject及其派生类,如果彼此之间存在一定联系,则应该尽量指定parent,对于QWidget应该指定parent或者加入布局管理器由管理器自动设置parent。
  2. 对象只需要在局部作用域存在时可以选择不进行内存分配,利用局部作用域变量的生命周期自动清理资源。
  3. 对于非QWidget的对象来说,如果不指定非NULLparent,则需要自己管理对象资源。QWidget比较特殊,我们在下一节讲解。
  4. 对于在局部作用域上创建的父对象及其子对象,要注意对象销毁的顺序,因为父对象销毁时也会销毁子对象,当子对象会在父对象之后被销毁时会引发double free。

QWidget和内存的释放

QWidget也是QObject的子类,所以在parent机制上是没有区别的,然而实际使用时我们更多的是使用“关闭”(close)而不是delete去删除控件,所以差异就出现了。

先提一下widget关闭的流程,首先用户触发close()槽,然后Qt向widget发送QCloseEvent,默认的QCloseEvent会做如下处理:

  1. 将widget隐藏,也就是hide()
  2. 如果有设置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.... 查看详情