总结一下Qt内存泄漏检测与处理策略,并附个人写的C++单例类,用于内存自动释放

简介: 总结一下Qt内存泄漏检测与处理策略,并附个人写的C++单例类,用于内存自动释放

如果时间急迫,不想看完整文章,可以直接去到文章末尾,看结论。有我个人写的C++单例类,用于内存自动释放。



1、QtCreator编写C++代码,怎么检测内存泄漏?


请参见本人的另一篇博文:https://blog.csdn.net/libaineu2004/article/details/104071627



2、Qt中控件new之后需不需要delete的问题


https://blog.csdn.net/Aidam_Bo/article/details/86303096

* QT的父子对象机制是在 QWidget和QOject中实现的。当我们使用父对象来创建一个对象的时候 ,父对象会把这个对象添加到自己的子对象列表中。当这个父对象被删除的时候,它会遍历它的子对象类表并且删除每一个子对象,然后子对象们自己再删除它们自己的子对象,这样递归调用直到所有对象都被删除。

* 这种父子对象机制会在很大程度上简化我们的内存管理工作,减少内存泄露的风险。

* 我们需要显式删除(就是用Delete删除)的对象是那些使用new创建的并且没有父对象的对象(切记是new的才要delete,

* 通过成员函数获得的对象,没有特殊说明的,千万不要随便delete。

* 如果我们在删除一个对象的父对象之前删除它,QT会自动地从它的父对象的子对象列表中移除它的。

* Qt自动回收不像Java这种,有垃圾回收机制。

* Qt自动回收是靠父子关系。父亲销毁了。他的孩子也销毁。

* 所以为什么main函数里面main widget/dialog/mainWindow是分配在栈上的原因。

* 其他new出来的东西都以这个widget作为父亲。 当程序最后结束了,main widget弹栈。

* 父类被销毁。子类跟着被销毁。 所以你自己new出来的控件,如果没有父类,自己又不删除,那就会造成内存泄漏。


小结--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无关)。



3、Qt智能指针和QObject对象树系统(父子系统)结合使用出现的问题


https://blog.csdn.net/gamesdev/article/details/8724090


* 首先要弄明白为什么qt只new不delete,父子对象管理模式先弄清楚,另外QScopedPoint有个接口data,返回正在管理的原始指针。

* QObject自有对象树系统(父子系统),它在和其它QObject子类进行交互的时候会将对方的指针保存起来,形成父子关系,

* 最终一个QObject子类指针会形成一个强大的树状结构,当父亲销毁的时候,会先销毁它的孩子(如果它的孩子是通过new操作符在堆上创建的话)。但是智能指针在保有QObject子类的时候会自动调用它的析构函数,从而引起事实上的两次delete,这个时候编译器的就会报错。

* 那我熟悉的QScopedPointer来说,本来将它用在类的成员中是一个很好的选择,但是由于它保有的是QObject的子类,这个智能指针在和其它QObject子类交互的时候难免会被对方保有原始指针的值,在进入类的析构函数,QScopedPointer保有原始指针的值会被先于释放并置为“已删除”的值0xfeeefeee,这个时候再通过智能指针的自动清理只可能会带来运行错误。在qscopedpointer.h源码中,我们看到QScopedPointerDeleter类的cleanup静态函数并不带有在delete之前的指针的值检测,于是在delete一个无效的指针时,错误发生了。



4、为什么官方的Qt示例和教程不使用智能指针?


目前得出了一个结论:只要加入了QObject对象树系统(父子机制),那么内存管理不是你的事儿了,你也不应该管,也不应该让智能指针管。

* 智能指针类std::unique_ptr和std::shared_ptr是内存管理。拥有这样一个智能指针意味着,你拥有指针。

* 但是,在QObject使用QObject父级创建或派生类型时,所有权(清理责任)将交给父级QObject。

* 在这种情况下,标准库智能指针是不必要的,甚至是危险的,因为它们可能会导致双重删除。

* 然而,当一个QObject在堆上创建而没有父类时,QObject情况就非常不同。在这种情况下,你不应该只保存一个原始指针,

* 而是一个智能指针,最好是一个std::unique_ptr对象。这样你就可以获得资源安全。

* 如果你稍后将对象所有权交给QObject你可以使用的父项std::unique_ptr<T>::release(),如下所示:

* auto obj = std::make_unique<MyObject>();

* // ... do some stuff that might throw ...

* QObject parentObject;

* obj->setParent( &parentObject );

* obj.release();



5、qDeleteAll与clear


https://blog.csdn.net/yao5hed/article/details/81092139


typedef struct _Defs
{
    int a;
    double d;
    QString s;
} Def;
QList<Def*> defs;
for(int i=0;i<500000;i++)
{
    Def* dd = new Def;
    dd->a = 12;
    dd->d = 4.23;
    dd->s = "jda";
    defs.append(dd);
}
qDebug()<<"before qDeleteAll: "<<defs.size();
qDeleteAll(defs);
qDebug()<<"after qDeleteAll: "<<defs.size();
defs.clear();

运行结果发现,不调用qDeleteAll的情况下,程序占内存78M;加上之后,只占内存12M。但是前后的size没有变化。


当T的类型为指针时,调用clear方法能置空,但并不能释放其内存。qDeleteAll可以释放容器元素内存,但没有对容器的置空操作,也就是size没变。所以qDeleteAll之后必须加上clear方法。



6、deleteLater


https://blog.csdn.net/yao5hed/article/details/81092168

void QObject::deleteLater()
{
    QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
}
bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    ......
    case QEvent::DeferredDelete:
        qDeleteInEventHandler(this);
        break;
    }
}
void qDeleteInEventHandler(QObject *o)
{
    delete o;
}


Qt中不建议手动delete掉QObject对象。原因一:不注意父子关系会导致某个对象析构两次,一次是手动析构,还有一次是parent析构,后者可能会出现delete堆上的对象。delete是C++和QT共有的一个操作符即时使用实时就析构删除了,而Qt里的deletelater的原理是:QObject::deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。 这样做的好处是可以在这些延迟删除的时间内完成一些操作,坏处就是内存释放会不及时。


应用例子:父窗体的子窗体在focusoutevent时deletelater();然后在主窗体绑定信号子窗体的destroy()信号和父窗体的槽函数,然后在槽函数中象父窗体调用父窗体的接口函数向父窗体发送子窗体的文本text();


7、QObjectCleanupHandler


https://blog.csdn.net/yao5hed/article/details/81092178


https://blog.csdn.net/luoyayun361/article/details/97250027


#include <QObjectCleanupHandler>
m_pCleanupHandler = new QObjectCleanupHandler();
m_pObj1 = new CObject();
m_pObj2 = new CObject();
m_pObj3 = new CObject();
m_pCleanupHandler->add(m_pObj1);
m_pCleanupHandler->add(m_pObj2);
m_pCleanupHandler->add(m_pObj3);
...
//最后只需要调用
m_pCleanupHandler->clear();

所有的对象都会全部释放。并且,如果其中有些对象已经在别的地方进行释放, 那就会自动从QObjectCleanupHandler管理列表中自动删除,不会重复删除。所以,即便是重复调用clear()也不会出问题。使用QObjectCleanupHandler进行资源管理非常方便。



x1、结论


1、QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象。我们不必手动删除内存。


2、程序员自己new出来的QObject及其派生类的对象,如果没有父类,自己又不删除,那就会造成内存泄漏。怎么办?我们可以使用QObjectCleanupHandler的机制来管理内存,在应用程序结束之前释放它们。


附上我写的单例类:


autocleanuphandler.h


#ifndef CAUTOLEANUPHANDLER_H
#define CAUTOLEANUPHANDLER_H
#include <QGlobalStatic>
#include <QObjectCleanupHandler>
#define AUTOCLEANUPHANDLER CAutoCleanupHandler::instance()
class CAutoCleanupHandler
{
public:
    CAutoCleanupHandler() {}
    virtual ~CAutoCleanupHandler() {}
public:
    static CAutoCleanupHandler *instance();
public:
    QObject *add(QObject *object);
    void remove(QObject *object);
    bool isEmpty() const;
    void clear();
private:
    QObjectCleanupHandler m_cleanupHandler;
};
#endif // CAUTOLEANUPHANDLER_H
autocleanuphandler.cpp
#include "autocleanuphandler.h"
Q_GLOBAL_STATIC(CAutoCleanupHandler, clean)
CAutoCleanupHandler *CAutoCleanupHandler::instance()
{
    return clean();
}
QObject *CAutoCleanupHandler::add(QObject *object)
{
    return m_cleanupHandler.add(object);
}
void CAutoCleanupHandler::remove(QObject *object)
{
    m_cleanupHandler.remove(object);
}
bool CAutoCleanupHandler::isEmpty() const
{
    return m_cleanupHandler.isEmpty();
}
void CAutoCleanupHandler::clear()
{
    m_cleanupHandler.clear();
}

使用举例:


//新建控件,无父类


FormOptionsWorkpiece *workpiece = new FormOptionsWorkpiece();


FormOptionsMotionctrlcard *motionctrlcard = new FormOptionsMotionctrlcard();


FormOptionsMachinetool *machinetool = new FormOptionsMachinetool();


FormOptionsCameraInstall *camerainstall = new FormOptionsCameraInstall();


//添加内存对象


AUTOCLEANUPHANDLER->add(workpiece);


AUTOCLEANUPHANDLER->add(motionctrlcard);


AUTOCLEANUPHANDLER->add(machinetool);


AUTOCLEANUPHANDLER->add(camerainstall);


//exe退出前,自动清理内存


AUTOCLEANUPHANDLER->clear();



x2、参考文献


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


https://blog.csdn.net/taiyang1987912/article/details/29271549

https://www.cnblogs.com/lsgxeva/p/7811288.html


Qt父子对象内存管理实现简析


https://www.dushibaiyu.com/2014/07/qt-fuzi-neicun.html

Qt智能指针官方文档


https://doc.qt.io/qt-5/qsharedpointer.html

https://doc.qt.io/qt-5/qscopedpointer.html

https://doc.qt.io/qt-5/qobjectcleanuphandler.html


相关文章
|
2月前
|
存储 缓存 NoSQL
工作 10 年!Redis 内存淘汰策略 LRU 和传统 LRU 差异,还傻傻分不清
小富带你深入解析Redis内存淘汰机制:LRU与LFU算法原理、实现方式及核心区别。揭秘Redis为何采用“近似LRU”,LFU如何解决频率老化问题,并结合实际场景教你如何选择合适策略,提升缓存命中率。
370 3
|
8月前
|
存储 分布式计算 监控
阿里云服务器实例经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i详解与选择策略
在阿里云现在的活动中,可选的云服务器实例规格主要有经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例,虽然阿里云在活动中提供了多种不同规格的云服务器实例,以满足不同用户和应用场景的需求。但是有的用户并不清楚他们的性能如何,应该如何选择。本文将详细介绍阿里云服务器中的经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例的性能、适用场景及选择参考,帮助用户根据自身需求做出更加精准的选择。
|
4月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
248 4
AI代理内存消耗过大?9种优化策略对比分析
|
4月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
187 26
|
3月前
|
机器学习/深度学习 监控 安全
解密虚拟化弹性内存:五大核心技术与实施策略
本文深入解析虚拟化环境中实现内存弹性管理的五大核心技术与实施策略。内容涵盖内存架构演进、关键技术原理、性能优化方法及典型问题解决方案,助力提升虚拟机密度与资源利用率。
198 0
|
3月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
149 0
|
弹性计算 安全 数据库
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
作为云计算服务核心组件,虚拟化内存管理直接影响业务系统性能表现。本文详解了内存优化方案与技术实践,助您降低30%资源浪费。
153 0
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
|
4月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
144 0
|
8月前
|
机器学习/深度学习 存储 PyTorch
PyTorch内存优化的10种策略总结:在有限资源环境下高效训练模型
在大规模深度学习模型训练中,GPU内存容量常成为瓶颈,特别是在训练大型语言模型和视觉Transformer时。本文系统介绍了多种内存优化策略,包括混合精度训练、低精度训练(如BF16)、梯度检查点、梯度累积、张量分片与分布式训练、
356 14
PyTorch内存优化的10种策略总结:在有限资源环境下高效训练模型
|
8月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略

推荐镜像

更多
  • qt