Qt中内存泄露和退出崩溃的问题 delete

Qt中帮程序员做了一些内存回收的事情,但正因为这些反而让对此不熟悉的人会屡屡犯错。

收录一篇不错的文章:

在C++中学习过程中,我们都知道:

  • delete 和 new 必须 配对使用(一 一对应):delete少了,则内存泄露,多了麻烦更大。

Qt作为C++的库,显然是不会违背C++的前述原则的。可是:

  • 在Qt中,我们很多时候都疯狂地用new,却很少用delete,缺少的 delete 去哪儿了?!

注:本文暂不涉及智能指针(smart pointer)相关的东西,你可以考虑 Qt 智能指针学习 一文

Qt半自动的内存管理

在Qt中,以下情况下你new出的对象你可以不用亲自去delete (但你应该清楚delete在何处被Qt调用的,怎么被调用的):

  • QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象(本文内容围绕这一点展开)

除此之外,有些类的对象可以接收设置一些特别的标记,比如:

  • QWidget及其派生类的对象,可以设置 Qt::WA_DeleteOnClose 标志位(当close时会析构该对象)
  • QAbstractAnimation派生类的对象,可以设置 QAbstractAnimation::DeleteWhenStopped
  • QRunnable::setAutoDelete()
  • MediaSource::setAutoDelete()
  • ...

注意:这些用法会有些陷阱,请注意看本文最后的3个小例子。

在Qt中,最基础和核心的类是:QObject 。它的魔力很大,本文只关注两点:

  • 父子关系
  • deleteLater

父子关系

在Qt中,每个 QObject 内部都有一个list,用来保存所有的 children,还有一个指针,保存自己的parent。当它自己析构时,它会将自己从parent的列表中删除,并且析构掉所有的children。

  • 注意:在 Qt 中,我们经常会遇到

    • 基类、派生类,或父类、子类。这是对于派生体系来说的,和在C++相关书中看到的完全一样,与这的parent无关
    • 父对象、子对象、父子关系。这是Qt中所特有的,也就是这儿的parent所引入的,与类的继承关系无关

建立与解除

QObject::~QObject () [virtual]
  • 当一个QObject对象析构时,它会将自己从父对象的 children 列表中移除(parent非0的话)
QObjectPrivate::setParent_helper(QObject *o)

获取父、子对象

每个QObject只有一个父对象:

const QObjectList & QObject::children () const

所以可以根据条件来查找喽:

T QObject::findChild ( const QString & name = QString() ) const
QList<T> QObject::findChildren ( const QString & name = QString() ) const

deleteLater

deleteLater 包含两层意思了

  • delete
  • later

呵呵,似乎这是废话哈。

删除自己

在去年春节前的时候吧,有人对

obj-> deleteLater()

会像下面一样调用delete:

delete obj;

感到不解。然后我写了这样一个C++例子:

class A
{
  public:
  A(){}
  void deleteMe()
  {
      delete this;
  }
};

int main()
{
  A * a = new A;
  a->deleteMe();
  return 0;
} 

应该不需要解释吧

later

Qt 是事件驱动的,所以发送一个删除事件到事件系统就可以啦:

void QObject::deleteLater()
{
    QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));
}

事件循环稍后看到该事件就会将其派发会这个widget:

bool QObject::event(QEvent *e)
{
    switch (e->type()) {
...
    case QEvent::DeferredDelete:
         ...

一些例子

无关痛痒?

很简短、很熟悉的一个例子是不?但是 如果你发现对象的析构函数始终不被成功调用,会有什么感觉?

#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel *label = new QLabel("Hello Qt!");
label->show();
return app.exec();
}

这是 C++ GUI Programming with Qt 4 一书的第一个例子。我们注意到这儿的 label 既没有指定parent,也没有对其调用delete。

所以,这儿会造成内存泄露。

书中解释说,对于这种小例子,这点内存泄露不算什么。不清楚官方这个例子的意图是什么,或许是一开始就让大家用指针吧。

三种改进方式

  • 分配对象到stack而不是heap中
QLabel label("Hello Qt!");
label.show();
  • 设置标志位,这样,当我们点击关闭按钮时,close()函数将会调用deleteLater
label->setAttribute(Qt::WA_DeleteOnClose);
  • 动手调用delete(不就是少了一个么,我们补上还不行么)
int ret = app.exec();
delete label;
return ret;

单独列一个吧

强化一下对前一个例子的了解

#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel label("Hello Qt!");
label.show();
label.setAttribute(Qt::WA_DeleteOnClose);
return app.exec();
}

运行正常,退出时会崩溃,因为label被close时,将会 delete 这儿label对象,但label对象却不是通过new分配到heap中的。

为了使得用户减少自己显式使用delete,Qt将delete隐藏的比较深。这样一来,不使用new为对象分配空间时,反倒需要多多小心了。

隐蔽很深?

看个小例子:这个程序退出时会直接崩溃。

#include <QtGui>
int main(int argc, char* argv[])
{
   QApplication app(argc, argv);
   QLabel label(tr"Hello Qt!");
   QWidget w;
   label.setParent(&w);
   w.show();
   return app.exec();
}
  • 问题出在哪儿呢?因为退出时,w 比 label 先被析构,当 w 被析构时,会删除chilren列表中的对象,也就是这儿的 label。但 label 却不是通过new分配在heap中,而是在stack中,可想而知,delete 一个再stack中的对象会怎么样了。相当于
QLabel label();
delete &label;
  • 两种改进办法:

    • 一是,将label分配到heap中
   QLabel *label = new QLabel("Hello Qt!");
   label.setParent(&w)
  • 再一种就是,确保label先于其parent被析构(调整一下顺序),这样,label析构时将自己从父对象的列表中移除自己,w析构时,children列表中就不会有分配在stack中的对象了。
   QWidget w;
   QLabel label(tr"Hello Qt!");

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

时间: 2024-10-06 15:51:21

Qt中内存泄露和退出崩溃的问题 delete的相关文章

Android开发过程中内存泄露检测

转自 http://blog.csdn.net/shimiso/article/details/44677041 一.内存泄露 内存泄漏会因为减少可用内存的数量从而降低计算机的性能.最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃. 内存泄漏可能不严重,甚至能够被常规的手段检测出来.在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放.这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果. 在以下情况,内存泄漏导致较严重的后果: 1)程

Android中内存泄露与如何有效避免OOM总结

一.关于OOM与内存泄露的概念 我们在Android开发过程中经常会遇到OOM的错误,这是因为我们在APP中没有考虑dalvik虚拟机内存消耗的问题. 1.什么是OOM OOM:即OutOfMemoery,顾名思义就是指内存溢出了.内存溢出是指APP向系统申请超过最大阀值的内存请求,系统不会再分配多余的空间,就会造成OOM error.在我们Android平台下,多数情况是出现在图片不当处理加载的时候. Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会

Java中内存泄露及垃圾回收机制

3 垃圾回收机制 3.1 什么是垃圾 垃圾,内存中的垃圾,即内存中已无效但又无法自动释放的空间.在Java语言中,没有引用句柄指向的类对象最容易成为垃圾.,产生垃圾的情况有很多,主要有以下3种: (1)       超出对象的引用句柄的作用域时,这个引用句柄引用的对象就变成垃圾. 例: { Person p1 = new Person(); …… } 引用句柄p1的作用域是从定义到“}”处,执行完这对大括号中的所有代码后,产生的Person对象就会变成垃圾,因为引用这个对象的句柄p1已超过其作用

java中内存泄露和内存溢出

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory! 内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产

Android中内存泄露

Android程序主要是由java代码写成的,我们都知道java在内存回收方面有着自己的一套体系,完全不需要coder来做内存销毁,但是在编程的时候还是有内存泄露的事儿发生,这种情况一般是因为什么呢? 首先我们要明确的是java的内存自动回收机制是怎么一回事:在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间. Java的内存垃圾回

Windows系统中内存泄露与检测工具及方法

1.检测需要使用的工具:windbg工具.检测前,需要先安装windbg工具.安装了该工具后,会在安装目录下有一个umdh工具.假设windbg安装在以下目录下:D:\Program Files\Debugging Tools for Windows (x86) 2.内存泄露检测技巧2.1 运行cmd窗口,将路径设置到安装路径下:2.2 在该cmd窗口中设置环境变量_NT_SYMBOL_PATH:D:\Program Files\Debugging Tools for Windows (x86)

JAVA中会存在内存泄露吗

所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中.java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉.由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的,例如下面的代码可以看到这种情况的内存回收: package com.huawei.interview; import java.io.IOException;

Java中会有内存泄露吗?

一.Java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.Java中对象是采用new或者反射的方法创建的, 这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的.GC为了能够正确释放对象,会监控每个对象的 运行状况,对他们的申请.引用.被引用.赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将 其回收,这样也可以消除引用循环的问题

关于Java中存不存在内存泄露问题

关于Java中存不存在内存泄露问题.我们首先要了解什么是内存泄露.在Java中内存泄露简而言之就是对象使用完,对象所占用的内存空间没有被释放,直至程序运行结束. 那么现在问题又来了!我们不是说Java拥有良好的垃圾回收机制吗?为什么还是存在内存泄露呢?这时候我们就有必要了解下GC的机制了.当在程序运行过程中,堆内存空间的对象没有被引用的时候,该堆内存空间就属于垃圾空间,就应该等待GC的回收,这是没有问题的.但是呢!还存在这样的一种情况,就是当堆内存空间被其他的对象(记为A对象)所引用的时候,就算