限制类对象生成的数量,如果我们需要的数值不是“1”,而是某个其它的某个值怎么办?最容易想到的办法是通过添加静态控制变量--计数器来达到目的。假设我们需要为打印机设计一个类,首先可能会这样设计:
class Printer{ public : class TooManyObjects{}; // 当需要的对象过多时就使用这个异常类 Printer(); Printer( const Printer & rhs); ~ Printer(); ... private : static size_t numObjects; }; // 定义静态类成员 size_t Printer::numObjects = 0 ; Printer::Printer(){ if (numObjects >= 1 ){ throw TooManyObjects(); } // 继续运行正常的构造函数; ... ++ numObjects;}Printer::Printer( const Printer & rhs){ if (numObjects >= 1 ){ throw TooManyObjects(); } // 继续运行正常的构造函数; ... ++ numObjects;}Printer:: ~ Printer(){ // 进行正常的析构函数处理; ... -- numObjects;}
核心思想就是使用numObjects跟踪Pritner对象存在的数量。当构造类时,它的值就增加,释放类时,它的值就减少。如果试图构造过多的Printer对象,就会抛出一个TooManyObjects类型的异常。这种方法可以很直观地把限制数量推广大到大于1的任何值。
但是,注意到这里的构造函数是公有的。这种方法可能会带来你意想不到的结果。假设我们一个特殊的打印机,是彩色打印机。这种打印机类有许多地方与普通的打印机类相同,所以我们从普通打印类继承下来:
class ColorPrinter: public Printer{ ... }; 现在假设我们系统有一个普通打印机和一个彩色打印机: Printer p;ColorPrinter cp;这两个定义会产生多少Pritner对象?答案是两个:一个是p,一个是cp。在运行时,当构造cp的基类部分时,会抛出TooManyObjects异常。对于许多程序员来说,这可不是他们所期望的事情。当其它对象包含Printer对象时,会发生同样的问题:
class FaxMachine; class CopyMachine; class CPFMachine{ // 一种机器,可以复印,打印,发传真。 private : Printer p; // 有打印能力 FaxMachine f; // 有传真能力 CopyMachine c; // 有复印能力 ...};CPFMachine m1; // 运行正常 CPFMachine m2; // 抛出 TooManyObjects异常问题是Printer对象能存在于三种不同的环境中:只有它们本身;作为其它派生类的基类;被嵌入在更大的对象里。存在这些不同环境,极大地混淆了跟踪“存在对象的数目” 的含义,因为你心目中的“对象的存在” 的含义与编译器不一致。因此,我们还是必须将构造函数放在private域内,并设计相应的伪构造函数。另外限制数值用静态常量代替。
class Printer{ public : class TooManyObjects{}; ~ Printer(); // 伪构造函数 static Printer * makePrinter(); static Printer * makePrinter( const Printer & rhs); ... private : static size_t numObjects; static const size_t maxObjects; Printer(); Printer( const Printer & rhs);}; // 定义静态类成员 size_t Printer::numObjects = 0 ; const size_t Printer::maxObjects = 10 ;Printer::Printer(){ if (numObjects >= maxObjects){ throw TooManyObjects(); } ... ++ numObjects;}Printer::Printer( const Printer & rhs){ if (numObjects >= maxObjects){ throw TooManyObjects(); } ... ++ numObjects;}Printer:: ~ Printer(){ ... -- numObjects;}Printer * Printer::makePrinter(){ return new Printer; }Printer * Printer::makePrinter( const Printer & rhs){ return new Printer(rhs); }至此,一个最多允许10个Printer对象存在的类定义好了。但是,如果我们有大量像Printer需要限制实例数量的类,就必须一遍又一遍地编写一样的代码,每个类编写一次。因此我们考虑把静态成员计数器(numObjects)和限制值(maxObjects)封装在一个类里,编写一个具有实例计数功能的基类,然后让像Printer这样的类从该基类继承。
通常的基类能达到这个目的吗?别忘了静态成员是隶属于类本身的。也就是说如果类A和类B都从这个基类继承,他们将共用基类的静态成员。这样岂不是会产生混乱,完全不能达到分别计数的目的。而且别忘了限制值(maxObjects)是一个静态常量,只能被一次赋值一次。因此我们需要确保每个进行实例计数的类都有一个相互隔离的计数器。使用计数类模板可以自动生成适当数量的计数器,能让计数器成为从模板中生成的类的静态成员。(给类模板赋予不同的模板参数值,将生成不同的实体类)
// Counted.h template < class BeingCounted > class Counted{ public : class TooManyObjects{}; // 用来抛出异常 static int objectCount(){ return numObjects; } protected : Counted(); Counted( const Counted & rhs); ~ Counted(){ -- numObjects; } private : static int numObjects; static const size_t maxObjects; void init(); // 避免构造函数的 }; // 代码重复 template < class BeingCounted > Counted < BeingCounted > ::Counted(){ init(); }template < class BeingCounted > Counted < BeingCounted > ::Counted( const Counted < BeingCounted >& ){ init(); }template < class BeingCounted > void Counted < BeingCounted > ::init(){ if (numObjects >= maxObjects) throw TooManyObjects(); ++ numObjects;} // Printer.h class Printer: private Counted < Printer > { public : // 伪构造函数 static Printer * makePrinter(); ~ Printer(); void submitJob(); using Counted < Printer > ::objectCount; // 让objectCount在Printer中是public // Counted<Printer>::objectCount; // 作用同上 using Counted < Printer > ::TooManyObjects; private : Printer(); Printer( const Printer & rhs); // 声明但不定义,禁止拷贝构造 }; // Printer.cpp #include " Printer.h " // 定义静态成员 int Counted < Printer > ::numObjects; const size_t Counted < Printer > ::maxObjects = 10 ;Printer * Printer::makePrinter(){ return new Printer();}Printer::Printer(){ ...}Printer:: ~ Printer(){ ...} void Printer::submitJob(){ cout << " Submit job " << endl;}
当Printer继承Counted<Printer>时,它可以忘记有关对象计数的事情。编写Printer类时根本不用考虑对象计数,就好像有其他人会为它计数一样。这里有趣的不是你所见到的东西,而是你看不到的东西。不检测对象的数量就好像限制将被超过,执行完构造函数后也不增加存在对象的数目。所有这些现在都由Counted<Printer>的构造函数来处理,因为Counted<Printer>是Printer的基类,我们知道Counted<Printer>的构造函数总在Printer的前面被调用。如果建立过多的对象,Counted<Printer>的构造函数就会抛出异常,甚至都没有调用Printer的构造函数。