C++模板元编程-Read.pdf
《C++模板元编程-Read.pdf》由会员分享,可在线阅读,更多相关《C++模板元编程-Read.pdf(74页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、目录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+中的
2、对象计数在 C+中,除非你要为其它事情分神,否则对某个特定类的所有已创建对象计数并不是一件很难的事。有时,简单就是简单,不过某些简单也往往很微妙。例如,假设你有一个类W idget,并且你想实现一种方法,以便在运行期获知到底有多少个Widget对象存在。一个既简单又正确的方法是,在 Widget中创建一个static计数器,每次调用Widget构造函数时就将计数器加1,而每次调用Widget析构函数时就将计数器减1同时,你还需要一个static成员函数howMany来报告当前有多少个Widget对象存在。如果Widget除了记录本类型对象个数以外什么也不做,那么它或多或少看上去应该是这样的:c
3、lass 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做计数工作,那么现在你的工作就完成了,不过你可能想
4、为多个类实现对象计数功能。反反复复做一件事是很乏味的,而乏味往往导致错误的出现。为了避免这种乏味,最好是将上面的实现对象计数功能的代码打包,这样就可以在任何需要对象计数功能的类中重用这些代码。理想的 包(package)应该是:容易使用的一一类的作者只需要做极少的工作就可以重用这些代码。理想情况是,他们不需要做更多的事,除了说“我想要为这个类添加对象计数功能”以外。高效的一一使用这个代码包的客户类不需要负担多余的空间和时间。安全可靠的一一基本不会产生错误的counto当构造函数抛出一个异常时,new和 delete之间的关联。当你需要C+动态分配一个对象时,你会使用类似下面的new表达式:cl
5、ass 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异常。这个类型的异常表示试图动态分配内存的行为失败了
6、。在上文中的new 表达式中,有两个函数可能会引发这个异常。第一个是调用operator n ew,它被认定可以找到足够的内存保存一个ABCD对象。第二个是接下来调用的ABCD构造函数,它被认定将原始内存(raw memory)转化为一个有效的ABCD对象。如果异常是调用operator new时引发的,那么没有内存被分配。但是,如果调用operatornew成功了,而调用ABCD构造函数时引发了异常,那么一个很重要的问题就是要把operatornew分配的内存释放掉。如果没有很好的解决这个问题,那么程序中就隐藏着内存泄漏的问题。对于客户(client)即请求创建ABCD对象的代码一一来说,确
7、定是哪一个函数引发异常是不可能的。多年以来,这都是C+语言规范草案中的一个漏洞,不过在1995年 3 月,C+标准委员会修订了相关规则,它规定,在一个new表达式中,如果operator new调用成功而其后的构造函数抛出一个异常,那么运行期(runtim e)系统必须自动释放由operator new分配的内存。释放内存的工作由operator delete(对应于operator new的释放内存的函数)执行。new表达式和operator delete之间这种微妙的关系影响了我们的对象实例计数的自动化操作。对于对象计数,你的解决方法十有八九是设计一个对象计数类。你的类很可能和我前面提到的
8、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来 完 成“记帐”。有两个显而易见的方法可以做到
9、这一点。一个方法是定义一个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:./原有
10、的私有/Widget 成员);这两种方法各有各的优点和缺点。不过在深入的考察他们之前,我们首先了解到这两种方法的当前版本都不能正常工作。问题在于Counter中的静态(static)对象counto这个count对象只有一个,而我们需要的是对应于每个使用Counter的对象都有一个count。例如,如果我们想要对Widget和 ABCD两个类实现计数功能,我们就需要两个静态(static)size_t对象,而不是一个。而将Counter:count改为非静态(nonstatic)对象并不能解决这个问题,因为我们需要的是每个类有一个co u n t,而不是每个对象。通 过 使 用 C+中的一个著
11、名但名字古怪的技巧,我们可以得到想要的东西:我们把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;/现在这行语句放在头文件中了现在第一个
12、Widget的实现看上去是这个样子的:/嵌入一个Counter对象来实现对象计数功能class Widget public:static size_t howManyOreturn Counter:howMany();private:Counter c;);而第二个实现则变成了这般模样:从Counter中派生,以实现对象计数功能class Widget:public Counter);注意,在这两种情况下我们是如何用Counter代 替 Counter的。正如我前面所说,每个使用Counter的类都以其自身为参数实例化这个模板。一个类以其自身为模板参数实例化一个模板,并将这个模板实例以为己用,
13、这个策略最早由Jim Coplien提出。他指出这个策略在许多语言中都有应用(不仅仅在C+中),并且他将其称为“a curiously recurringtemplate pattern(一个奇特的递归模板模式)模式的命名和其它许多事情一样大有学问,我也并不十分擅长此道,不过我或许会把这个模式称为“Dolt For Me(为我做)”,或者其它类似的名字。至少,每个由Counter衍生出来的类都为将Counter实例化的类提供了一种服 务(它记录有多少个对象存在)。于 是 类 Counter对 W idget对象们计数,而类Counter则 对 ABCD对象们计数。现 在 Counter是一个模
14、板了,无论嵌入的设计还是继承的设计都能够正常工作,是我们把它们的优缺点比较一番的时候了。我们的一个设计准则是客户必须能够很容易的实现对象计数功能,而且前面的代码已经表述的很明白,基于继承的设计要比基于嵌入的设计容易。因为前者只要求将Counter作为一个基类,而后者则要求定义一个Counter数据成员,并且在客户代码中重新实现howMany以调用Counter的 howMany2这并不是什么很复杂的工作(客户代码中的howMany只是简单的inline函数),但是只用做一件事比必须做两件事来得简单。因此,我们先来考察基于继承的设计方案。使用公共继承基于继承的设计之所以能够工作,是因为C+保证每
15、当一个派生类对象被创建或销毁时,它的基类部分会被首先创建,并且被最后销毁。因此每当以Counter为基类的派生类的一个对象被创建和销毁时Counter的构造函数和析构函数就会被分别调用。每次涉及到基类,我们必须考虑关于虚拟析构函数的问题。Counter应该具有析构函数吗?现有的 C+面向对象设计法则显示的确应该如此。如果Counter不具有虚拟的析构函数,那么通过一个基类指针delete 一个派生类对象将导致未定义(通常也是不希望的)的结果:class Widget:public Counter );Counter*pw=new Widget;/使一个基类指针指向一个派生类对象delete p
16、w;/导致未定义的结果/如果基类没有虚拟析构函数的话上面的行为可能会产生无法预料的后果,这违反了“我们的对象计数的设计必须是安全可靠的”这一设计准则。让 Counter具有一个虚拟析构函数也就有了足够的理由。但是,另一个设计准则是效率最大化(完成对象计数不会耗费任何不必要的时间和不会占用任何不必要空间),现在麻烦来了。这是因为Counter中存在着一个虚拟析构函数(存在其它虚拟函数也一样),也就是说每个Counter类(或其派生类)的对象都要具有一个(隐藏的)虚拟指针,而如果本来的对象并不支持虚拟函数,这个虚拟指针的存在就会使对象的体积变大。这意味着,如果Widget本身不包含虚拟函数,那么从
17、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,不能调用私有的/ope
18、rator delete不幸的是一一这才是最有意思的地方一一new表达式也不能编译了!Counter*pw=new Widget;/这条语句也无法通过编译,因为/operator delete 是私有的还记得我们前面关于new,delete和异常的讨论吗?如果后续的构造函数调用失败,C+运行期(runtime)系统必须将operator new分配的内存空间释放掉。还有,系统会调用operatordelete函数完成释放内存的工作。但是我们把Counter类的operator delete声明为私有函数了,这导致了无法通过new在 堆(heap)中创建对象!是的,这的确有悖常理,而且如果你的编
19、译器还不支持这条规则也不用吃惊,但是我所描述的行为才是正确的。此外,没有其它简单易行的方法可以防止通过Counter*指针销毁派生类对象,并且我们已经否定了在Counter中提供虚拟虚构函数的想法。所以我说我们还是放弃这个设计,试试使用一个Counter数据成员吧。使用一个数据成员我们已经见到基于Counter数据成员的设计有一个缺点:客户代码不但要定义一个Counter数据成员,还要编写一个inline版的howMany函数来调用Counter的howMany函数。这只比我们希望强加于客户代码上的工作多不了多少,但是这使它非常难以管理。此外还存在着另一个缺陷。通常在一个类中添加一个Count
20、er数据成员将会使该类的对象体积增加。考虑Counter的定义:tempi ateclass Counter public:Counter();Counter(const Counter&);Counter。;static size_t howManyO;private:static size_t count;);注意,它的所有数据成员都是静态(static)的。这就是说Counter类的每个对象都是什么也没有。或许我们应该认为Counter类对象的大小是0?或许,但这样认为对我们一点好处也没有。C+在这一点上是很明确的。任何对象的大小至少是一个字节,即使是没有非静态数据成员的对象也是如此。由
21、此规则可知,sizeof Counter模板的每个实例类都会产生一个正整数。因此每个包含Counter类对象的客户类都比其不包含Counter类对象时具有更多的数据。(有趣的是,这并不表示一个包含Counter类对象的类的大小一定比其不包含Counter类对象时大。这是因为边界对齐在捣乱。例如,如果Widget是一个包含两个字节数据的类,而 系 统 进 行 4 字节的对齐,那 么 每 个 W idget类对象都会包含两个字节的占位符,而sizeof(Widget)将返回4。如果,通常情况下,编译器为了保证每个对象的大小都大于0 而在Counterv Widget 中插入 了一个 c h a r
22、,那么即使 Widget 包含了一个 Counted Widget 对象,sizeof(Widget)也很可能仍然返回4。这个Counter对象只是简单的占用了 Widget中占位符所占用的一个字节。这种情况并不是经常出现,因此我们在设计对象计数功能包时不考虑这种情况。)使用私有继承回头再看看基于继承的设计代码,就是那个需要在Counter中考虑虚拟析构函数的设计:class Widget:public Counter -;Counter*pw=new Widget;deletepw;/导致未定义的后果/如果Counter类缺少一个虚拟/析构函数早先我们试图通过禁止编译delete表达式来防止
23、以上这一系列操作,不过我们发现这样做也导致new 表达式不能被编译。但是我们可以禁止其它一些操作。比如我们可以禁止由Widget*指针到Counter*指针的隐式转换。换句话说,我们可以禁止派生类指针到基类指针的转换。我们所要做的只是将公共继承替换为私有继承:class Widget:private Counler -;Counter*pw=new Widget;/错误!不能进行/由 Widget*Counter*的隐式转换止 匕外,我们很可能会发现将Counter作为基类不会增加Widget的体积。是的,我记得我刚刚说过0 大小的类是不存在的,但是一一好吧,这并不是我的本意。我所要说的是0大
24、小的对象是不存在的。C+标准中明确的指出,派 生 类(直接或间接)的基类部分的大小可以是0。事实上,许多编译器通过空基类优化来达到这个要求。因此,如果一个Widget包含一个Counter,那么Widget的大小肯定会增加。Counter数据成员本身就是一个对象,所以它的大小不可能是0。但是如果Widget派生自Counter,编译器就可以保持Widget的大小不变。这导出了一个有趣的设计经验,这条经验适用于内存空间紧张并且需要考虑空类(大小为0 的类)的情况:如果私有继承和包含都能很好的工作,那么私有继承由于包含。最后的这个设计已经近乎完美了。由于自Counter派生不会为派生类增加任何每个
25、对象都要具有的数据,并且所有Counter的成员函数都是inline的,这就允许你的编译器实现空类优化,满足了效率要求。由于计数操作由Counter的成员函数自动完成,而这些函数则由C+自动调用,并且使用了虚拟继承来防止隐式转化(隐式转换允许像操作基类对象一样操作派生类对象),因此也满足了安全性的要求(0 K,并不是绝对的安全:W idget的作者可能会愚蠢的使用另一个类型实例化C ounter,比如Widget可能会派生自Countero我决定忽略这种情况)。这个设计当然也很容易被客户代码使用,不过有些人也许会嘀嘀咕咕,说还可以再容易些。使用私有继承意味着再派生类中howMany将称为私有成
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 模板 编程 Read
限制150内