More Effective C++ 阅读笔记(十二)--怎样限制实例化对象的数量

 More Effective C++ 阅读笔记(十二)--怎样限制实例化对象的数量
黑月亮 发表于 2005-10-5 14:56:00 

       这种技术很容易推广到限制对象为任何数量上。我们只需把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类的计数器是静态变量numObjects,我们应该把变量放入实例计数类中。然而也需要确保每个进行实例计数的类都有一个相互隔离的计数器。使用计数类模板可以自动生成适当数量的计数器,因为我们能让计数器成为从模板中生成的类的静态成员:
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类,这样使用Counted模板:
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继承的原因(参见Effective C++条款42)。另一种方法是在Printer和counted<Printer>之间使用public继承,但是我们必须给Counted类一个虚拟析构函数。(否则如果有人通过Counted<Printer>*指针删除一个Printer对象,我们就有导致对象行为不正确的风险——参见Effective C++条款14。)条款M24已经说得很明白了,在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没有被定义。
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值