快速导航php
1、 回顾历史
2、 智能指针简介
3、 Delphi中的interface
4、 Delphi中智能指针的实现
5、 interface + 泛型 = 强类型的智能指针!
6、 智能指针与集合
7、 注意事项
8、 总结 html
本随笔全部源代码打包下载 c++
在c++中,对象能够建立在栈里,也能够建立在堆里。如:程序员
// 如下代码建立栈对象ide
栈对象生命周期由后台管理。当方法结束时,栈对象会从栈中弹出,编译器会自动销毁栈所弹出的对象。函数
// 如下代码建立堆对象post
堆对象保存在堆中,堆对象生命周期不受后台管理,程序员必须本身手动的释放堆对象,不然会形成内存泄露:性能
Pascal语言从OOP Pascal开始支持面向对象,也就是说,OOP Pascal支持建立对象了。OOP Pascal和c++同样,也能够分别建立栈对象和堆对象:测试
咱们最多见的OOP Pascal堆对象的定义和建立:spa
运行结果:
OOP Pascal也有栈对象,栈对象的定义和建立:
运行结果:
从结果咱们能够看到,与c++不一样的是,OOP Pascal所谓的栈对象的构造和析构,不受constructor方法和destructor方法控制,咱们不能捕获到OOP Pascal栈对象的构造和析构。
通过前面分析,咱们知道,栈对象的声明周期由后台管理,栈对象在声明时进行构造,当方法退出或者类被销毁时(此时栈对象为类的成员变量),栈对象的生命周期也会随着结束,后台自动会调用它们的析构函数并释放栈空间。
而堆对象必须由程序员手动的释放,若是一个方法只有一两个堆对象咱们还能应付的过来,可是当堆对象很是多,并且堆对象通常都要通过多个方法的传递、赋值,传递到最后,很是容易忘了delete,形成内存泄露。
能不能让后台也去自动管理堆对象的释放呢?前辈们想到一个办法,就是让一个栈对象包含一个堆对象的引用,当栈对象被后台自动释放时,会调用栈对象的析构函数,因而,在栈对象的析构函数里写下delete堆对象指针的语句。这样,就完成了后台间接管理堆对象,以上就是stl中的智能指针auto_ptr的处理方法。
从智能指针的简介中咱们能够了解到,要使用智能指针,咱们必须得捕获到栈对象的构造函数,将堆对象的指针传入栈对象,由栈对象保存堆对象的指针;还必须捕获到栈对象的析构函数,在栈对象的析构函数里进行对构造函数所传入堆对象指针delete。在c++很容易作到这一点,可是经上面分析,咱们没法对Delphi的栈对象进行构造和析构的捕获。
咱们能够换一种角度思考,不必定非要是栈对象,只要在Delphi中能有一种东西,只要出了它的做用域,它就能自动析构!
Delphi中的interface能间接知足咱们这个须要,请看如下例子:
有结果能够看到,代码中没有释放testInter指向的对象,对象由后台释放了。若是将1*处改成testInter: TTestInterface;则结果以下,咱们将看到若是不声明为接口,即便建立同一个对象,Delphi是不会自动释放对象的。
在此,咱们利用了接口的自动管理功能,它本身维护着一个引用计数,当引用计数为0时接口本身会调用析构函数。关于Delphi接口的一些概念以及为何后台会自动释放接口,能够参考如下两篇文章,在此不作多余叙述。
一、 Delphi 的接口机制浅探http://www.d99net.net/article.asp?id=206
二、 浅谈引用计数http://www.moon-soft.com/doc/13056.htm
有了以上经验,咱们就能够实现咱们的智能指针了!
首先,咱们要建立一个继承于TInterfacedObject的对象,在构造函数中传入要管理的堆对象的引用,在析构函数里FreeAndNil这个堆对象的引用。代码以下:
而后咱们写一个控制台程序作试验:
代码执行结果以下图所示:
若是咱们将代码2*处替换成
TClassicalAutoPtr.Create (tt);
执行结果将看不到Destroy,析构函数没有被调用。由于由TClassicalAutoPtr.New返回的是一个interface,而TClassicalAutoPtr.Create返回的是一个Object。
这样,咱们一个简单的智能指针就完成了。
D2009引入了泛型,咱们把程序稍微改动一下,就能够支持强类型的智能指针了!
关于D2009对泛型的支持的分析,请参看我另外两篇随笔:
http://www.cnblogs.com/felixYeou/archive/2008/08/22/1273989.html
http://www.cnblogs.com/felixYeou/archive/2008/08/22/1274202.html
咱们以stl的auto_ptr做为参照物,要是我们的智能指针看起来“优雅”,必须还要实现如下几个方法:
一、 Get:返回智能指针所指向的对象
二、 Release:释放智能指对堆对象的管理,智能指针被自动释放后,不对堆对象进行释放
三、 Reset:为智能指针指向其它堆对象,同时释放原来指向的堆对象
对于auto_ptr一些运算符重载,这里不考虑在内,由于Delphi2009尚未支持类的运算符重载。
话很少说了,直接上代码:
智能指针类代码:
测试代码:
测试结果为:
然而咱们将3*处代码改为
ap.Release.DoPrintInt,则输出结果为
由于Release方法已经通知智能指针无论理堆对象了。
同时,咱们还能够把DoTestAutoPtr方法写成这样,或许这样建立TTestClass对象更优美一些:
tt.DoPrintInt;
// 不须要使用tt.Free;
若是咱们声明一个全局变量:
并从DoTestAutoPtr方法开始改变其下代码:
结果以下:
咱们能够看到,当调用完毕DoTestAutoPtr方法后,方法内的堆对象tt并无销毁,这说明智能指针ap并无销毁。
由于在DoTestAutoPtr方法最后一行,将ap接口变量赋值给了全局变量gAp,此时接口的引用计数+1,方法退出后,ap变量被销毁,接口的引用计数-1,可是gAp仍然引用着对象,因此引用计数不为0。当运行到第4*步时,强制把gAp指向空地址,对象的引用计数-1,为0,这个时候后台自动调用对象的析构函数Destroy(这有点像Java或.net的垃圾回收机制)。因此,咱们使用智能指针,能够放心的建立,放心的引用,而不用去管何时该销毁,彻底由后台帮咱们实现。
下面把测试程序改一下,让智能指针与集合结合测试:
测试结果:
一、智能指针与堆对象之间的循环引用
假如咱们把TTestClass类进行以下修改,让堆对象拥有指向它智能指针的引用:
同时,把测试方法进行以下修改:
此时,咱们获得了很是不靠谱的结果:
智能指针居然没有自动释放!
从上面的分析和前面的代码咱们能够看到,接口的引用计数为0的时候,接口会自动释放,咱们要保证接口可以被顺利的释放,必须保证接口的引用计数为0。
从第 5* 点代码咱们能够看到,tt.Ap := ap,使得智能指针与堆对象之间进行了循环引用,致使接口ap的引用计数+1为2。最后在方法退出的时候,虽然ap占用的引用已经被释放了,引用-1,可是因为堆对象tt不会本身释放,因此堆对象tt.Ap所占用的引用没有释放,方法在退出时,接口的引用数为1,接口没有自动释放。
二、什么使用时候使用Release方法
首先咱们为测试单元加入use:Generics.Collections,再将TTestClass类修改以下:
此时,成员变量再也不是一个值类型,而是一个引用类型。
将从DoTestAutoPtr方法开始代码修改以下:
此时,咱们在DoTestAutoPtr方法内部建立了智能指针,并将智能指针所指向的堆对象传给全局变量,而后在DoTestAutoPtr方法执行结束后调用全局变量的DoPrintInt方法。运行结果:
运行失败了,缘由是在DoTestAutoPtr方法退出了之后,TAutoPtr<TTestClass>.New(TTestClass.Create(10))语句所建立的接口引用计数为0,此时它会调用TTestClass的Destroy方法将fList销毁。此时,咱们调用DoPrintInt方法,想获得fList第一个元素,可是fList自己已经被销毁了,因此致使错误的发生。
咱们将第6*行改成:
gTt := TAutoPtr<TTestClass>.New(TTestClass.Create(10)).Release;
运行结果:
此时不会出现错误,由于Release方法已经通知智能指针堆对象已经不受智能指针管理,因此在TAutoPtr<TTestClass>销毁的时候不会调用 TTestClass的析构函数,fList得以保留下来。
在此处咱们能够看到,因为堆对象再也不受到智能指针的管理,因此咱们必须手动的将其释放FreeAndNil(gTt),不然就会产生上图所发生的结果:内存泄露。
刚开始实现栈对象我考虑过使用record,Delphi的record很是相似于类,保存在栈中,支持方法、属性和带参数的构造函数,可是不支持析构函数,因此没有办法实现咱们的智能指针。Delphi版的智能指针很早就在cnPack讨论区中有前辈提出来过了(http://bbs.cnpack.org/viewthread.php?tid=1399),可是使用起来不方便致使这种写法不怎么流行。自从D2009支持泛型之后,之前不少实现起来很麻烦的功能如今都能很简单的实现,如智能指针与泛型集合的结合。可是,在Delphi中使用智能指针是稍微有一些性能损失的,在目前电脑速度愈来愈快的今天,这点损失已经显得微不足道了。
本随笔全部源代码打包下载:http://files.cnblogs.com/felixYeou/auto_ptr_code.rar