C++模板元编程-Read.pdf
目录C+中的对象计数.2资源管理.9整体处理异常-22比快排更快的排序方法.24C+模板:过犹不及.26部分辩论.29C+模板兀编程.-.-.-.-.-.-.32中英术语比较.48C+部分知识点.501、内联函数(INLINE)-502、重载函数 OPERATOR OVERLOADING-513、友元函数-524 输入/输出:和-525、建立和删除-54建立-576、常量的正确性-577、继承-598、构造函数与析构函数-639、程序风格指导-6510、程序语言之间的小转换-6611、指向成员函数的指针-6912、模板-7113、静态及其它-73C+怎么定义预先不知道大小的数组?.74C+中的对象计数在 C+中,除非你要为其它事情分神,否则对某个特定类的所有已创建对象计数并不是一件很难的事。有时,简单就是简单,不过某些简单也往往很微妙。例如,假设你有一个类W idget,并且你想实现一种方法,以便在运行期获知到底有多少个Widget对象存在。一个既简单又正确的方法是,在 Widget中创建一个static计数器,每次调用Widget构造函数时就将计数器加1,而每次调用Widget析构函数时就将计数器减1同时,你还需要一个static成员函数howMany来报告当前有多少个Widget对象存在。如果Widget除了记录本类型对象个数以外什么也不做,那么它或多或少看上去应该是这样的:class Widget(public:Widget()+count;Widget(const Widgel&)+count;Widget()count;static size_t howManyO return count;private:static size_t count;);/count的定义是必须的/以下是在一个实现文件中的定义size_t Widget:count=0;这段代码会工作的很好。唯一需要小心一点的是,别忘了实现拷贝构造函数,因为编译器为Widget生成的拷贝构造函数并不知道要对count加 1。如果你只是想对Widget做计数工作,那么现在你的工作就完成了,不过你可能想为多个类实现对象计数功能。反反复复做一件事是很乏味的,而乏味往往导致错误的出现。为了避免这种乏味,最好是将上面的实现对象计数功能的代码打包,这样就可以在任何需要对象计数功能的类中重用这些代码。理想的 包(package)应该是:容易使用的一一类的作者只需要做极少的工作就可以重用这些代码。理想情况是,他们不需要做更多的事,除了说“我想要为这个类添加对象计数功能”以外。高效的一一使用这个代码包的客户类不需要负担多余的空间和时间。安全可靠的一一基本不会产生错误的counto当构造函数抛出一个异常时,new和 delete之间的关联。当你需要C+动态分配一个对象时,你会使用类似下面的new表达式:class ABCD.;ABCD=A Big Complex Datatype(一个大而复杂的数据类型)ABCD*p=new ABCD;/一个 new 表达式N ew 表达式一一它的含义内建于语言中,而且你无法改变它的行为一一会做两件事。首先,它调用一个内存分配函数,这个函数称为operator new。这个函数负责寻找足够大的内存来保存一个ABCD对象。如果operator new的调用成功了,new表达式就会在operatornew找到的内存空间上调用一个ABCD的构造函数。现在假设operator new抛出了一个std:bad_alloc异常。这个类型的异常表示试图动态分配内存的行为失败了。在上文中的new 表达式中,有两个函数可能会引发这个异常。第一个是调用operator n ew,它被认定可以找到足够的内存保存一个ABCD对象。第二个是接下来调用的ABCD构造函数,它被认定将原始内存(raw memory)转化为一个有效的ABCD对象。如果异常是调用operator new时引发的,那么没有内存被分配。但是,如果调用operatornew成功了,而调用ABCD构造函数时引发了异常,那么一个很重要的问题就是要把operatornew分配的内存释放掉。如果没有很好的解决这个问题,那么程序中就隐藏着内存泄漏的问题。对于客户(client)即请求创建ABCD对象的代码一一来说,确定是哪一个函数引发异常是不可能的。多年以来,这都是C+语言规范草案中的一个漏洞,不过在1995年 3 月,C+标准委员会修订了相关规则,它规定,在一个new表达式中,如果operator new调用成功而其后的构造函数抛出一个异常,那么运行期(runtim e)系统必须自动释放由operator new分配的内存。释放内存的工作由operator delete(对应于operator new的释放内存的函数)执行。new表达式和operator delete之间这种微妙的关系影响了我们的对象实例计数的自动化操作。对于对象计数,你的解决方法十有八九是设计一个对象计数类。你的类很可能和我前面提到的Widget类极为相似,甚至可能是完全一样的:/下面的内容讲述了为什么这种设计不完全正确class Counter public:Counter()+count;)Counter(const Counter&)+count;Counter。count;static size_t howMany()return count;private:static size_t count;);/下面的代码仍然放置在一个实现文件中size_t Counter:count=0;这个设计的主要思想是:需要对存在的对象进行计数的类的作者只需要简单的使用Counter来 完 成“记帐”。有两个显而易见的方法可以做到这一点。一个方法是定义一个Counter对象作为一个类的数据成员,比如:/嵌入一个Counter对象来实现对象计数功能class Widget public:./原有的公共(public)/Widget 成员static size_t howManyO return Counter:howMany();private:./原有的私有(private)/Widget 成员Counter c;);另一个方法是将Counter作为基类,例如:/从 Counter中派生,以实现对象计数功能class Widget:public Counter)./原有的公共/Widget 成员private:./原有的私有/Widget 成员);这两种方法各有各的优点和缺点。不过在深入的考察他们之前,我们首先了解到这两种方法的当前版本都不能正常工作。问题在于Counter中的静态(static)对象counto这个count对象只有一个,而我们需要的是对应于每个使用Counter的对象都有一个count。例如,如果我们想要对Widget和 ABCD两个类实现计数功能,我们就需要两个静态(static)size_t对象,而不是一个。而将Counter:count改为非静态(nonstatic)对象并不能解决这个问题,因为我们需要的是每个类有一个co u n t,而不是每个对象。通 过 使 用 C+中的一个著名但名字古怪的技巧,我们可以得到想要的东西:我们把Counter转化为一个模板,而每个使用Counter的类则以自己为模板参数实例化这个模板。让我们再来看看。现在Counter变成了一个模板:templateclass Counter public:Counter()+count;Counter(const Counter&)+count;Counter。count;static size_t howManyO return count;private:static size_t count;);templatesize_tCounter:count=0;/现在这行语句放在头文件中了现在第一个Widget的实现看上去是这个样子的:/嵌入一个Counter对象来实现对象计数功能class Widget public:static size_t howManyOreturn Counter:howMany();private:Counter c;);而第二个实现则变成了这般模样:从Counter中派生,以实现对象计数功能class Widget:public Counter);注意,在这两种情况下我们是如何用Counter代 替 Counter的。正如我前面所说,每个使用Counter的类都以其自身为参数实例化这个模板。一个类以其自身为模板参数实例化一个模板,并将这个模板实例以为己用,这个策略最早由Jim Coplien提出。他指出这个策略在许多语言中都有应用(不仅仅在C+中),并且他将其称为“a curiously recurringtemplate pattern(一个奇特的递归模板模式)模式的命名和其它许多事情一样大有学问,我也并不十分擅长此道,不过我或许会把这个模式称为“Dolt For Me(为我做)”,或者其它类似的名字。至少,每个由Counter衍生出来的类都为将Counter实例化的类提供了一种服 务(它记录有多少个对象存在)。于 是 类 Counter对 W idget对象们计数,而类Counter则 对 ABCD对象们计数。现 在 Counter是一个模板了,无论嵌入的设计还是继承的设计都能够正常工作,是我们把它们的优缺点比较一番的时候了。我们的一个设计准则是客户必须能够很容易的实现对象计数功能,而且前面的代码已经表述的很明白,基于继承的设计要比基于嵌入的设计容易。因为前者只要求将Counter作为一个基类,而后者则要求定义一个Counter数据成员,并且在客户代码中重新实现howMany以调用Counter的 howMany2这并不是什么很复杂的工作(客户代码中的howMany只是简单的inline函数),但是只用做一件事比必须做两件事来得简单。因此,我们先来考察基于继承的设计方案。使用公共继承基于继承的设计之所以能够工作,是因为C+保证每当一个派生类对象被创建或销毁时,它的基类部分会被首先创建,并且被最后销毁。因此每当以Counter为基类的派生类的一个对象被创建和销毁时Counter的构造函数和析构函数就会被分别调用。每次涉及到基类,我们必须考虑关于虚拟析构函数的问题。Counter应该具有析构函数吗?现有的 C+面向对象设计法则显示的确应该如此。如果Counter不具有虚拟的析构函数,那么通过一个基类指针delete 一个派生类对象将导致未定义(通常也是不希望的)的结果:class Widget:public Counter );Counter*pw=new Widget;/使一个基类指针指向一个派生类对象delete pw;/导致未定义的结果/如果基类没有虚拟析构函数的话上面的行为可能会产生无法预料的后果,这违反了“我们的对象计数的设计必须是安全可靠的”这一设计准则。让 Counter具有一个虚拟析构函数也就有了足够的理由。但是,另一个设计准则是效率最大化(完成对象计数不会耗费任何不必要的时间和不会占用任何不必要空间),现在麻烦来了。这是因为Counter中存在着一个虚拟析构函数(存在其它虚拟函数也一样),也就是说每个Counter类(或其派生类)的对象都要具有一个(隐藏的)虚拟指针,而如果本来的对象并不支持虚拟函数,这个虚拟指针的存在就会使对象的体积变大。这意味着,如果Widget本身不包含虚拟函数,那么从Counter中派生出(直接或间接)W idget,就会导致Widget类的对象体积膨胀。这可不是我们想要的。解决这个问题的唯一途径是避免客户代码通过基类指针销毁派生类对象。看起来将Counter的 operator delete声明为私有函数可以达到这个目的:templateclass Counter public:private:void operator delete(void*););现在delete表达式将无法通过编译:class Widget:public Counter .;Counter*pw=new Widget;.delete pw;/E rro r,不能调用私有的/operator delete不幸的是一一这才是最有意思的地方一一new表达式也不能编译了!Counter*pw=new Widget;/这条语句也无法通过编译,因为/operator delete 是私有的还记得我们前面关于new,delete和异常的讨论吗?如果后续的构造函数调用失败,C+运行期(runtime)系统必须将operator new分配的内存空间释放掉。还有,系统会调用operatordelete函数完成释放内存的工作。但是我们把Counter类的operator delete声明为私有函数了,这导致了无法通过new在 堆(heap)中创建对象!是的,这的确有悖常理,而且如果你的编译器还不支持这条规则也不用吃惊,但是我所描述的行为才是正确的。此外,没有其它简单易行的方法可以防止通过Counter*指针销毁派生类对象,并且我们已经否定了在Counter中提供虚拟虚构函数的想法。所以我说我们还是放弃这个设计,试试使用一个Counter数据成员吧。使用一个数据成员我们已经见到基于Counter数据成员的设计有一个缺点:客户代码不但要定义一个Counter数据成员,还要编写一个inline版的howMany函数来调用Counter的howMany函数。这只比我们希望强加于客户代码上的工作多不了多少,但是这使它非常难以管理。此外还存在着另一个缺陷。通常在一个类中添加一个Counter数据成员将会使该类的对象体积增加。考虑Counter的定义:tempi ateclass Counter public:Counter();Counter(const Counter&);Counter。;static size_t howManyO;private:static size_t count;);注意,它的所有数据成员都是静态(static)的。这就是说Counter类的每个对象都是什么也没有。或许我们应该认为Counter类对象的大小是0?或许,但这样认为对我们一点好处也没有。C+在这一点上是很明确的。任何对象的大小至少是一个字节,即使是没有非静态数据成员的对象也是如此。由此规则可知,sizeof Counter模板的每个实例类都会产生一个正整数。因此每个包含Counter类对象的客户类都比其不包含Counter类对象时具有更多的数据。(有趣的是,这并不表示一个包含Counter类对象的类的大小一定比其不包含Counter类对象时大。这是因为边界对齐在捣乱。例如,如果Widget是一个包含两个字节数据的类,而 系 统 进 行 4 字节的对齐,那 么 每 个 W idget类对象都会包含两个字节的占位符,而sizeof(Widget)将返回4。如果,通常情况下,编译器为了保证每个对象的大小都大于0 而在Counterv Widget 中插入 了一个 c h a r,那么即使 Widget 包含了一个 Counted Widget 对象,sizeof(Widget)也很可能仍然返回4。这个Counter对象只是简单的占用了 Widget中占位符所占用的一个字节。这种情况并不是经常出现,因此我们在设计对象计数功能包时不考虑这种情况。)使用私有继承回头再看看基于继承的设计代码,就是那个需要在Counter中考虑虚拟析构函数的设计:class Widget:public Counter -;Counter*pw=new Widget;deletepw;/导致未定义的后果/如果Counter类缺少一个虚拟/析构函数早先我们试图通过禁止编译delete表达式来防止以上这一系列操作,不过我们发现这样做也导致new 表达式不能被编译。但是我们可以禁止其它一些操作。比如我们可以禁止由Widget*指针到Counter*指针的隐式转换。换句话说,我们可以禁止派生类指针到基类指针的转换。我们所要做的只是将公共继承替换为私有继承:class Widget:private Counler -;Counter*pw=new Widget;/错误!不能进行/由 Widget*Counter*的隐式转换止 匕外,我们很可能会发现将Counter作为基类不会增加Widget的体积。是的,我记得我刚刚说过0 大小的类是不存在的,但是一一好吧,这并不是我的本意。我所要说的是0大小的对象是不存在的。C+标准中明确的指出,派 生 类(直接或间接)的基类部分的大小可以是0。事实上,许多编译器通过空基类优化来达到这个要求。因此,如果一个Widget包含一个Counter,那么Widget的大小肯定会增加。Counter数据成员本身就是一个对象,所以它的大小不可能是0。但是如果Widget派生自Counter,编译器就可以保持Widget的大小不变。这导出了一个有趣的设计经验,这条经验适用于内存空间紧张并且需要考虑空类(大小为0 的类)的情况:如果私有继承和包含都能很好的工作,那么私有继承由于包含。最后的这个设计已经近乎完美了。由于自Counter派生不会为派生类增加任何每个对象都要具有的数据,并且所有Counter的成员函数都是inline的,这就允许你的编译器实现空类优化,满足了效率要求。由于计数操作由Counter的成员函数自动完成,而这些函数则由C+自动调用,并且使用了虚拟继承来防止隐式转化(隐式转换允许像操作基类对象一样操作派生类对象),因此也满足了安全性的要求(0 K,并不是绝对的安全:W idget的作者可能会愚蠢的使用另一个类型实例化C ounter,比如Widget可能会派生自Countero我决定忽略这种情况)。这个设计当然也很容易被客户代码使用,不过有些人也许会嘀嘀咕咕,说还可以再容易些。使用私有继承意味着再派生类中howMany将称为私有成员,所以派生类必须必须包含一个using声明来让howMany称为公共成员:class Widget:private Counter public:使 howMany成为公共成员using Counter:howMany;./Widget的其它部分没有改变);class ABCD:private Counter public:使 howMany成为公共成员using Counter:howMany;/ABCD的其它部分没有改变);对于那些不支持名字空间(namespace)的编译器,可以使用老式的访问声明(已废弃)代替using声明:class Widget:private Counter public:/使 howMany成为公共成员Counter:howMany;.”Widget的其它部分没有改变);现在,那些想要实现对象计数功能,并且想把计数值开放(作为类的接控)给它们的客户的客户代码必须要做两件事:将 Counter作为基类,使 howMany可访问 5。不过使用继承要注意两种情况。第一个是二义性。假设我们像对Widget对象们计数,并且我们想把计数值开放。根据前面的讨论,我们从Counter中派生W idget,然后在 W idget中 将 howMany声明为公共成员。现在假设我们有一个公共继承自W idget类的Special Widget类,我们也想为Special Widet的客户提供Widget所提供的功能。没问题,我们只需从 Counter中派生出 SpecialWidget 即可。但是这样就导致了二义性问题。SpecialWidget应该开放哪一个howMany?是继承自W idget的那一个,还 是 继 承 自 CounterWidget 的那一个?自然,我们希望的是继承自Counter的那一个 howM any,但是除了写出 SpecialWidget:howMany 以外,没有其它任何方法可以做到这一点。幸运的是,SpecialWidget:howMany只是个简单的inline函数:class SpecialWidget:public Widget,private Counter public:static size_t howMany()return Counter:howMany();I;第 二 个 要 注 意 的 是,如 果 我 们 使 用 继 承 的 方 法 实 现 对 象 计 数,那么我们由Widget:howMany得到的计数值不仅包括了 Widget对象的数目,也包括所有派生自Widget类的子类对象的数目。如果SpecialWidget是唯一一个派生自Widget的类,并且存在着五个独立的Widget对象和三个独立的SpecialWidget对象,那么Widget:howMany将返回8。毕竞,每个SpecialWidget对象的构造函数都要调用基类Widget的构造函数。小结以下几点是你需要记住的:对象自动计数并不难实现,但也不是轻而易举的事。使 用“Dolt For me”设计模式(Coplien的 curiously recurring template”模式)使正确的生成计数值成为可能。使用私有继承则使“提供对象计数功能的同时不增加对象的大小”成为可能。当客户代码可以选择“从一个空类继承”或“包含一个空类对象作为数据成员”时,优先选用继承,因为它允许对象的结构更紧凑。在调用堆对象的构造函数时,C+会尽量避免内存泄漏问题,因此需要访问operatornew的程序通常也需要访问相应的operator deleteo Counter类模板并不在意你是将其作为基类,还是将其对象作为数据成员。它对着两种情况一视同仁。因此,客户代码可以自由的选择是派生还是包含,甚至可以在同一个程序的不同部分使用不同的设计策略。返回首页资源管理我最喜欢的对资源的定义是:任何在你的程序中获得并在此后释放的东西。”内存是一个相当明显的资源的例子。它需要用new 来获得,用 delete来释放。同时也有许多其它类型的资源文件句柄、重要的片断、Windows中的GD I资源,等等。将资源的概念推广到程序中创建、释放的所有对象也是十分方便的,无论对象是在堆中分配的还是在栈中或者是在全局作用于内生命的。对于给定的资源的拥有者,是负责释放资源的一个对象或者是一段代码.所有权分立为两种级别-自动的和显式的(automatic and explicit),如果一个对象的释放是由语言本身的机制来保证的,这个对象就是被自动地所有。例如,一个嵌入在其他对象中的对象,他的清除需要其他对象来在清除的时候保证。外面的对象被看作嵌入类的所有者。类似地,每个在栈上创建的对象(作为自动变量)的释放(破坏)是在控制流离开了对象被定义的作用域的时候保证的。这种情况下,作用于被看作是对象的所有者。注意所有的自动所有权都是和语言的其他机制相容的,包括异常。无论是如何退出作用域的-正常流程控制退出、一个 break语句、一个 return、一个goto、或者是一个throw-自动资源都可以被清除。到目前为止,一切都很好!问题是在引入指针、句柄和抽象的时候产生的。如果通过一个指针访问一个对象的话,比如对象在堆中分配,C+不自动地关注它的释放。程序员必须明确的用适当的程序方法来释放这些资源。比如说,如果一个对象是通过调用new 来创建的,它 需 要 用 delete来回收。一 个 文 件 是 用 CreateFile(Win32 API)打开的,它需要用CloseHandle 来 关 闭。用 EnterCritialSection 进 入 的 临 界 区(Critical Section)需要LeaveCriticalSection退出,等等。一个“裸”指针,文件句柄,或者临界区状态没有所有者来确保它们的最终释放。基本的资源管理的前提就是确保每个资源都有他们的所有者。第一规则一个指针,一个句柄,一个临界区状态只有在我们将它们封装入对象的时候才会拥有所有者。这就是我们的第一规则:在构造函数中分配资源,在析构函数中释放资源。当你按照规则将所有资源封装的时候,你可以保证你的程序中没有任何的资源泄露。这点在当封装对象(Encapsulating O bject)在栈中建立或者嵌入在其他的对象中的时候非常明显。但是对那些动态申请的对象呢?不要急!任何动态申请的东西都被看作一种资源,并且要按照上面提到的方法进行封装。这一对象封装对象的链不得不在某个地方终止。它最终终止在最高级的所有者,臼动的或者是静态的。这些分别是对离开作用域或者程序时释放资源的保证。下面是资源封装的一个经典例子。在一个多线程的应用程序中,线程之间共享对象的问题是通过用这样一个对象联系临界区来解决的。每一个需要访问共享资源的客户需要获得临界区。例如,这可能是Win32下临界区的实现方法。代码:class CritSect(friend class Lock;public:CrilSect()InitializeCriticalSection(&_critSection);)CritSect()DeleteCriticalSection(&_critSection);private:void Acquire()(EnterCriticalSection(&_critSection);)void Release()(LeaveCriticalSection(&_critSection);)CRITICAL_SECTION _critSection;);这里聪明的部分是我们确保每一个进入临界区的客户最后都可以离开。“进入“临界区的状态是一种资源,并应当被封装。封装器通常被称作一个锁(lock)。代码:class Lockpublic:Lock(CritSect&critSect):_critSect(critSect)_critSect.Acquire();)-Lock()(_critSect.Release();Iprivate:CritSect&_critSect;);锁一般的用法如下:代码:void Shared:Act()throw(char*)(Lock lock(_critSect);/perform action-may throw/automatic destructor of lock)注意无论发生什么,临界区都会借助于语言的机制保证释放。还有一件需要记住的事情-每一种资源都需要被分别封装。这是因为资源分配是一个非常容易出错的操作,是要资源是有限提供的。我们会假设一个失败的资源分配会导致一个异常-事实上,这会经常的发生。所以如果你想试图用一个石头打两只鸟的话,或者在一个构造函数中申请两种形式的资源(例如构造函数中new两次内存,可能失败哦),你可能就会陷入麻烦。只要想想在一种资源分配成功但另一种失败抛出异常时会发生什么。因为构造函数还没有全部完成,析构函数不可能被调用,第一种资源就会发生泄露。这种情况可以非常简单的避免。无论何时你有一个需要两种以上资源的类时,写两个笑的封装器将它们嵌入你的类中。每一个嵌入的构造都可以保证删除,即使包装类没有构造完成。Smart Pointers我们至今还没有讨论最常见类型的资源-用操作符new分配,此后用指针访问的一个对象。我们需要为每个对象分别定义一个封装类吗?(事实上,C+标准模板库已经有了一个模板类,叫做auto_ptr,其作用就是提供这种封装。我们一会儿再回到auto_ptr。)让我们从一个极其简单、呆板但安全的东西开始。看下面的Smart Pointer模板类,它十分坚固,甚至无法实现。代码:template class SPtr(public:-SPtr()delete_p;/_p指向的内存未必是new得到的,危险!T*operator-()return _p;T const*operator-()const return _p;protected:SPtr():_p(0)explicit SPtr(T*p):_p(p)T*_p;为什么要把SPtr的构造函数设计为protected呢?如果我需要遵守第一条规则,那么我就必须这样做。资源-在这里是classT的一个对象-必须在封装器的构造函数中分配。但是我不能只简单的调用new T,因为我不知道T 的构造函数的参数。因为,在原则上,每一个T 都有一个不同的构造函数;我需要为他定义个另外一个封装器。模板的用处会很大,为每一个新的类,我可以通过继承SPtr定义一个新的封装器,并且提供一个特定的构造函数。代码:class SItem:public SPtr(public:explicit SItem(int i):SPtr(new Item(i)为每一个类提供一个Smart Pointer真的值得吗?说实话-不!他很有教学的价值,但是一旦你学会如何遵循第一规则的话,你就可以放松规则并使用一些高级的技术。这一技术是让 SPtr的构造函数成为public,但是只是是用它来做资源转换(Resource Transfer)我的意思是用new操作符的结果直接作为SPtr的构造函数的参数,像这样:代码:SPtr item(new Item(i);这个方法明显更需要自控性,不只是你,而且包括你的程序小组的每个成员。他们都必须发誓除了作资源转换外不把构造函数用在任意其他用途。幸运的是,这条规矩很容易得以加强。只需要在源文件中查找所有的new即可。Resource Transfer到目前为止,我们所讨论的一直是生命周期在一个单独的作用域内的资源。现在我们要解决一个困难的问题-如何在不同的作用域间安全的传递资源。这一问题在当你处理容器的时候会变得十分明显。你可以动态的创建一串对象,将它们存放至一个容器中,然后将它们取出,并且在最终安排它们。为了能够让这安全的工作-没有泄露-对象需要改变其所有者。这个问题的一个非常显而易见的解决方法是使用Smart Pointer,无论是在加入容器前还是还找到它们以后。这是他如何运作的,你加入Release方法到Smart Pointer中:代码:template T*SPtr:Release()T*pTmp=_p;_p=0;return pTmp;这样把wrapper内部的资源返回到外界!注意在Release调用以后,Smart Pointer就不再是对象的所有者了-它内部的指针指向空。现在,调用了 Release都必须是一个负责的人并且迅速隐藏返回的指针到新的所有者对象中。在我们的例子中,容器调用了 R elease,比如这个Stack的例子:代码:void Stack:Push(SPtr&item)throw(char*)(if(_top=maxStack)throw Stack overflow;_arr _top+=item.Release();;同样的,你也可以在你的代码中用加强Release的可靠性。相 应 的 P叩 方法要做些什么呢?他应该释放了资源并祈祷调用它的是一个负责的人而且立即作一个资源传递它到一个Smart Pointer?这听起来并不好。Strong Pointers资源管理在内容索引(Windows NT Server上的一部分,现在是Windows 2000)上工作,并且,我对这十分满意。然后我开始想这一方法是在这样一个完整的系统中形成的,如果可以把它内建入语言的本身岂不是一件非常好?我提出了强指针(Strong Pointer)和弱指针(Weak Pointer)一个Strong Pointer会在许多地方和我们这个SPtr相似-它在超出它的作用域后会清除他所指向的对象。资源传递会以强指针赋值的形式进行。也可以有Weak Pointer存在,它们用来访问对象而不需要所有对象-比如可赋值的引用。任何指针都必须声明为Strong或者W eak,并且语言应该来关注类型转换的规定。例如,你不可以将Weak Pointer传递到一个需要Strong Pointer的地方,但是相反却可以。Push方法可以接受一个 Strong Pointer并且将它转移到Slack中的Strong Pointer的序列中。Pop方法将会返回一个Strong Pointero 把 Strong Pointer的引入语言将会使垃圾回收成为历史。这里还有一个小问题-修改C+标准几乎和竞选美国总统一样容易。当我将我的注意告诉给Bjarne Stroutrup的时候,他看我的眼神好像是我刚刚要向他借一千美元一样。然后我突然想到一个念头。我可以自己实现Strong Pointers,毕竟,它们都很象SmartPointers 给它们一个拷贝构造函数并重载赋值操作符并不是一个大问题。事实上,这正是标准库中的auto_ptr有的。重要的是对这些操作给出一个资源转移的语法,但是这也不是很难。代码:template SPtr:SPtr(SPtr&ptr)(_p=ptr.Release();)template void SPtr:operator=(SPtr&ptr)(if(_p!=ptr._p)delete _p;_p=ptr.Release();使这整个想法迅速成功的原因之一是我可以以值方式传递这种封装指针!我有了我的蛋糕,并且也可以吃了。看这个Stack的新的实现:代码:class Stack(enum maxStack=3;public:Stack():_top(0)()void Push(SPtr&item)throw(char*)(if(_top=maxStack)throw Stack overflow;_arr _top+=item;/这里要求push进来的SPtr所指向的内容是new出来的SPtr Pop()(if(_top=0)return SPtr();return _aiT _top;int _top;SPtr _arr maxStack;Pop方法强制客户将其返回值赋给一个Strong Pointer,SPtro任何试图将他对一个普通指针的赋值都会产生一个编译期错误,因为类型不匹配。此外,因 为 P o p 以值方式返回一个 Strong Pointer(在Pop的声明时SPtr后面没有&符号),编译器在return时自动进行了一个资源转换。他调用了 operator=来从数组中提取一个Item,拷贝构造函数将他传递给调用者。调用者最后拥有了指向Pop赋值的Strong Pointer指向的一个Item。我马上意识到我已经在某些东西之上了。我开始用了新的方法重写原来的代码。分 析 器(Parser)我过去有一个老的算术操作分析器,是用老的资源管理的技术写的。分析器的作用是在分析树中生成节点,节点是动态分配的。例如分析器的Expression方法生成一个表达式节点。我没有时间用Strong Pointer去重写这个分析器。我令Expression、Term和 Factor方法以传值的方式将Strong Pointer返回到Node中。看下面的Expression方法的实现:代码:SPtr Parser:Expression()/Parse a termSPtr pNode=Term();EToken token=_scanner.Token();if(token=tPlus|token=tMinus)(/Expr:=Term (+-)Term SPtr pMultiNode=new SumNode(pNode);do(_scanner.Accept();SPtr pRight=Term();pMultiNode-AddChild(pRight,(token=tPlus);token=_scanner.Token();)while(token=tPlus|token=tMinus);pNode=up_cast(pMultiNode);)/otherwise Expr:=Termreturn pNode;/by value!)最开始,Term方法被调用。他传值返回一