限制某个类能产生的对象数量

众所周知:只能生成一个对象的是单例模式,但是看一下情况:

假设定义一个Printer 类:

建立 Printer 对象 p1;  
使用 p1;  
释放 p1;  
建立Printer对象p2;  
使用 p2;  
释放 p2;  
... 

这种设计在同一时间里没有实例化多个Printer对象, 而是在程序的不同部分使用了不
同的Printer对象。不允许这样编写有些不合理。毕竟我们没有违反只能存在一个printer
的约束。就没有办法使它合法化么? 


当然有:

class Printer { 
public: 
  class TooManyObjects{};   //异常类


  // 伪构造函数 
  static Printer * makePrinter();  


  ~Printer();  


  void submitJob(const PrintJob& job); 


  void reset(); 


  void performSelfTest(); 
  ...  


private: 
  static size_t numObjects;  
  Printer();  
  Printer(const Printer& rhs);        //我们不定义这个函数 
};                                       //因为不允许 
                                                  //进行拷贝 
                                   

// Obligatory definition of class static 
size_t Printer::numObjects = 0;  


Printer::Printer() 
{   

if (numObjects >= 1) { 
    throw TooManyObjects(); 
  }  
  继续运行正常的构造函数;  
  ++numObjects; 
}  


Printer * Printer::makePrinter() 
{ return new Printer; } 


当需要的对象过多时,会抛出异常,如果你认为这种方式给你的感觉是unreasonably 
harsh,你可以让伪构造函数返回一个空指针。当然用户在使用之前应该进行检测。

 
除了用户必须调用伪构造函数,而不是真正的构造函数之外,它们使用Printer类就象
使用其他类一样: 
Printer p1;                               // 错误! 缺省构造函数是private 
Printer *p2 = 
  Printer::makePrinter();           // 正确, 间接调用缺省构造函数 
Printer p3 = *p2;                     // 错误! 拷贝构造函数是private 
p2->performSelfTest();            // 所有其它的函数都可以 
p2->reset();                             // 正常调用  
...  
delete p2;                                // 避免内存泄漏,如果 
                                               // p2 是一个 auto_ptr, 
                                               // 就不需要这步。



这种技术很容易推广到限制对象为任何数量上。我们只需把hard-wired常量值1改为
根据某个类而确定的数量,然后消除拷贝对象的约束。例如,下面这个经过修改的Printer类的代码实现,最多允许10个Printer对象存在: 


class Printer { 
public: 
  class TooManyObjects{};  


  // 伪构造函数 
  static Printer * makePrinter(); 
  static Printer * makePrinter(const Printer& rhs);  
  ...  


private: 
  static size_t numObjects;   

  static const size_t maxObjects = 10;       // 见下面解释  
  Printer(); 
  Printer(const Printer& rhs); 
};  

// Obligatory definitions of class statics 
size_t Printer::numObjects = 0; 
const size_t Printer::maxObjects;  


Printer::Printer() 

  if (numObjects >= maxObjects) { 
    throw TooManyObjects(); 
  }  
  ...  
}  


Printer::Printer(const Printer& rhs) 

  if (numObjects >= maxObjects) { 
    throw TooManyObjects(); 
  }  
  ...  
}  


Printer * Printer::makePrinter() 
{ return new Printer; }  


Printer * Printer::makePrinter(const Printer& rhs) 

{ return new Printer(rhs); } 


如果你的编译器不能编译上述类中Printer::maxObjects的声明,这丝毫也不奇怪。特
别是应该做好准备, 编译器不能编译把10做为初值赋给这个变量这条语句。 给static  const成员(例如int, char, enum等等)确定初值的功能是最近才加入到C++中的,所以一些编译器还不允许这样编写。如果没有及时更新你的编译器,可以把maxObjects声明为在一个private内匿名枚举类型里的枚举元素, 
class Printer { 
private: 
  enum { maxObjects = 10 };                // 在类中, 
  ...                                      // maxObjects为常量10  

};  

 
或者象non-const static成员一样初始化static常量: 
class Printer { 
private: 
  static const size_t maxObjects;            // 没有赋给初值  
  ...  
};  
// 放在一个代码实现的文件中 
const size_t Printer::maxObjects = 10; 


    后面这种方法与原来的方法有一样的效果, 但是显示地确定初值能让其他程序员更容易理解。当你的编译器支持在类定义中给const static成员赋初值的功能时,你应该尽可能地利用这个功能。


一个具有对象计数功能的基类 

如果我们有大量像Printer需要限制实例数量的类,就必须一遍又一遍地编写一样的代码,每个类编写一次。这将会使大脑变得麻木。应该有一种方法能够自动处理这些事情。难道没有方法把实例计数的思想封装在一个类里吗? 


  我们很容易地能够编写一个具有实例计数功能的基类, 然后让像Printer这样的类从该
基类继承,而且我们能做得更好。

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; 


    从这个模板生成的类仅仅能被做为基类使用,因此构造函数和析构函数被声明为
protected。注意private成员函数init用来避免两个Counted构造函数的语句重复。


 现在我们能修改Printer类

class Printer: private Counted<Printer> { 
public: 
  // 伪构造函数 
  static Printer * makePrinter(); 
  static Printer * makePrinter(const Printer& rhs);  
  ~Printer();  


  void submitJob(const PrintJob& job); 
  void reset(); 
  void performSelfTest(); 
  ...  
  using Counted<Printer>::objectCount;     // 参见下面解释 
  using Counted<Printer>::TooManyObjects;  // 参见下面解释  


private: 
  Printer(); 
  Printer(const Printer& rhs); 

}; 
    

Printer使用了Counter模板来跟踪存在多少Printer对象,坦率地说,除了Printer
的编写者,没有人关心这个事实。它的实现细节最好是 private,这就是为什么这里使用
private继承的原因。另一种方法是在Printer和counted<Printer>之间使用public继承,但是我们必须给Counted类一个虚拟析构函数。(否则如果有人通过Counted<Printer>*指针删除一个Printer对象,我们就有导致对象行为不正确的风险)。在Counted中存在虚函数,几乎肯定影响从Counted继承下来的对象的大小和布局。我们不想引入这些额外的负担,所以使用private继承来避免这些负担。 


    Counted所做的大部分工作对于Printer的用户来说都是隐藏的,但是这些用户可能很
想知道有当前多少Printer对象存在。Counted模板提供了objectCount函数,用来提供这
种信息,但是因为我们使用private继承,这个函数在Printer类中成为了private。为了
恢复该函数的public访问权,我们使用using声明: 
class Printer: private Counted<Printer> { 
public: 
  ... 
  using Counted<Printer>::objectCount; // 让这个函数对于Printer 
                                       //是public 
  ...     
}; 
    

这样做是合乎语法规则的,但是如果你的编译器不支持命名空间,编译器就不允许这样做。如果这样的话,你应使用老式的访问权声明语法: 
class Printer: private Counted<Printer> { 
public: 
  ... 
  Counted<Printer>::objectCount;       // 让objectCount 
                                       // 在Printer中是public 
  ...  
}; 

这种更传统的语法与uning声明具有相同的含义。但是我们不赞成这样做。
TooManyObjects类应该也应用同样的方式来处理,因为Printer的客户端如果要捕获这种
异常类型,它们必须有能力访问TooManyObjects。当Printer继承Counted<Printer>时,
它可以忘记有关对象计数的事情。编写Printer类时根本不用考虑对象计数,就好像有其他人会为它计数一样。

Printer的构造函数可以是这样的: Printer::Printer() 

  进行正常的构造函数运行 

    


这里有趣的不是你所见到的东西,而是你看不到的东西。不检测对象的数量就好像限制
将被超过,执行完构造函数后也不增加存在对象的数目。所有这些现在都是由Counted<Printer>的构造函数来处理,因为Counted<Printer>是Printer的基类,我们知
道Counted<Printer>的构造函数总在 Printer 的前面被调用。如果建立过多的对象,
Counted<Printer>的构造函数就会抛出异常,甚至都没有调用Printer的构造函数。 
    

最后还有一点需要注意,必须定义Counted内的静态成员。对于numObjects来说,这
很容易——我们只需要在Counted的实现文件里定义它即可:

 
template<class BeingCounted>                 // 定义numObjects 
int Counted<BeingCounted>::numObjects;       // 自动把它初始化为0 


    对于maxObjects来说,则有一些技巧。我们应该把它初始化为什么值呢?如果你想允许建立10个printer对象,我们应该初始化Counted<Printer>::maxObjects为10。另一方面如果我们向允许建立16个文件描述符对象,我们应该初始化Counted<Printer>::maxObjects为16。到底应该怎么做呢? 


    简单的方法就是什么也不做。我们不对maxObject进行初始化。而是让此类的客户端提供合适的初始化。


Printer的作者必须把这条语句加入到一个实现文件里: 

const size_t Counted<Printer>::maxObjects = 10; 

同样FileDescriptor的作者也得加入这条语句: 
const size_t Counted<FileDescriptor>::maxObjects = 16; 


    如果这些作者忘了对maxObjects进行初始化,会发生什么情况呢?很简单:连接时会
发生错误,因为maxObjects没有被定义。如果我们提供了充分的文档对Counted客户端说明了需求,他们会回去加上这个必须的初始化。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值