Essential Qt 第九章 内存管理

              前面连续几章制作了一个记事本(ReadMe)程序,当然并没有完成所有的功能。作为一名C++程序员,不知道你有没有注意到一个问题,在这个程序中有个类ReadMe,在类的构造函数中使用了大量的new来创建对象,然而这些构造函数中的new却找不到对应的delete,事实上这个类根本就没有析构函数。如果你有这样的疑惑,很好。。。。因为这对于C++程序员来说很正常。
              Qt提供了一套内存管理机制,这里将会介绍这套机制使用,或者说让你知道哪些必须delete,哪些不必delete.事实上大多数C++有关内存的问题都可以写成长篇论文或者一本大头书,这里则只是简单的介绍下Qt内存管理上的一些原理,并不讨论Qt管理内存的实现细节。
               在正式开始之前,我们先看一个简单的C++的类定义,以及类构造函数
class Student
{
  private:
    string Name;
    int Age;
  public:
    Student();
};


他的构造函数有两个写法
写法1
Student::Student()
{
  Name = "Jack";
  Age = 20;
}
或者这样
写法2
Student::Student():Name("Jack"),Age(20)
{
}
这两种写法有个最大的区别在于,第一种写法中Name = "Jack",他这里并不是使用了string类的构造函数,而是使用了复制函数,C++规定, 对象的成员变量的初始化动作发生在进入构造函数之前,也就是说,第一种写法事实上先调用string类的默认构造函数创建了一个空的对象Name,然后再调用复制函数来给这个对象Name赋值,相对于写法2只调用一次构造函数,写法1调用了两次string类的函数,这种写法必然会降低程序的效率。
          如果从程序效率来讲,写法2是最佳选择,但这里也带来一个问题,如果类成员比较多呢?以前面完成的ReadMe程序为例,他的成员就有满满一页,而这只是我们完成的“小玩具”,如果是工业化的大型程序,一个构造函数如果要使用写法2的话可能是另一回事了,因为出于模块化的设计初衷,我们在ReadMe程序中就通过:就把变量放在不同的函数中,然后在构造函数中调用这些函数,这样的方法来实现,如果采用写法2,那就必须把长长的一串变量写成满满一页的列表,而如果以后需要修改程序的话会变成一场灾难了。
           我们可以看出写法1和写法2其实算上各有利弊,写法1使得编码较为容易,写法2就更加考虑程序的性能。这里就需要一种比较折中的办法了,写法1之所以效率较低,因为需要调用2次函数,但如果有些类“ 复制表现的像初始化一样好”,换句话说,有些类成员的默认初始函数和复制函数的开销非常小,那这样的类成员可以采用写法1,如果类成员复制构造函数开销较大,则采用写法2

           在明确了这点原则后再来看ReadMe的构造函数,他有长长一串类变量,但很欣慰的是这些变量都是指针,无论一个指针指向一个多大的内存,指针本身很小,很显然指针的赋值开销非常小,完全可以采用写法1,事实上我也是这么做的。这里也许有人会追问,为什么要用指针,而不是一个类的对象呢?这里原因有两个,首先,这里源自图形界面“庞大”的内存开销,一个很普通的按钮QPushButton,这样一个窗体控件需要多大内存呢,这是一个无法回答的问题,如果你用一张1Mb的图片作为这个按钮的背景和用一张5Mb的图片做背景是完全不一样的,所以面对这种情况,在运行时决定内存大小远优于在编译时决定内存大小。其次,使用指针的另一个重要的原因就是Qt的大多数类(其实我遇到的所有类都是这样)都不能使用复制构造函数和赋值符号=,这两者在Qt的类中 将相应的成员函数声明为private,并且不予实现,这样避免了用会直接使用复制构造函数或者通过继承来实现复制功能,所以如果不使用指针的话,在构造函数中根本无法给类对象赋值,应为在构造函数体内使用的是复制构造函数,而Qt这样设计的原因在于Qt本身的特点,例如信号与槽,如果复制的对象里有个槽,一个信号连接到两个完全一样的槽里,另外对于界面编程来说复制本身就没有太大意义,界面中极少会出现连个完全相同的窗体部件。

         另外需要说明的是在ReadMe程序中,虽然ReadMe类成员都是“运行时决定内存”,但最后的ReadMe类对象确实创建在栈中,即在编译时就决定了内存大小,那这样会不会占用过大的内存呢?其实仔细的观察下ReadMe的类成员话就会发现,这个类成员都是一些指针,换言之,虽然最后创建了一个在栈中的对象,但仅仅是一个包含了很多指针的对象,这个对象本身所需要的内存是非常小的。

          然后就是大家最关心的delete了,在ReadMe的构造函数中,有一串的new而没有一个对应的delete,那在ReadMe对象销毁的时候,这些成员变量有被销毁吗?答案是肯定,Qt以窗体部件中的父/子关系来进行内存管理,一个类对象A在调用析构函数之前,会先行调用子对象B的析构函数,如果子对象B也有子对象C,则对象B在析构函数调用前先调用C的析构函数,即A是B的父对象,B是C的父对象,关闭A的时候会按照C->B->A的顺序来逐个析构,通过这样的方式来保证内存不会泄漏,以ReadMe程序为例,关闭这个程序的时候系统会调用他的析构函数,在这之前,会优先调用类成员的析构函数,这些类成员都是他的子对象或者孙对象(其实Qt文档里没有孙对象这个概念,只是为了描述问题临时起的。。)。这样我们可以确保ReadMe类对象在析构前他的成员已经被析构了,那这个对象也就没有其他内存可以释放,所以ReadMe程序就不需要显示的写一个析构函数,这里也可以看出父/子关系的重要性,对于一个Qt程序,必须且只能有一个父对象,其他Qt类的对象都必须是这个对象的(直接或间接)子对象。
          但假设ReadMe函数类成员里有个string* names;在他的构造函数里有这样一行代码
names = new string;
这不是一个Qt的类,显然Qt通过父/子对象来管理回收内存的方法不适合他,如果遇到这样的情况就必须实现ReadMe的析构函数了
ReadMe::~ReadMe()
{
  delete names;
}


PS:本章引用了<Effective C++>的内容
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值