1. 针对某些有特殊功能的类,有时需要限制其对象数量,例如系统中只有一个打印机,因此需要将打印机的对象数目限制为1,或者只有16个file descriptor(文件描述器)可用,因此必须确定不会有更多的descriptor objects被产生出来,在这些情况下.就需要限制对象数目,或者说阻止对象被产生出来.
2. 允许零个或一个对象
要限制对象数目,最直接的策略是限制构造函数的调用(以下称策略1),采用这种思想将构造函数设为private,然后声明一个友元函数调用它,并生成一个static对象,像这样:
class PrintJob;
class Printer {
public:
void submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
friend Printer& thePrinter();//友元函数
private:
Printer();//默认构造函数
Printer(const Printer& rhs);//copy constructor
...
};
Printer& thePrinter(){
static Printer p;
return p;
}
当要使用Printer对象时,就调用thePrinter,它返回Printer的引用且保证只产生一个Printer对象,除了将thePrinter声明为friend,还可以使它成为Printer类的static成员,像这样:
class Printer {
public:
static Printer& thePrinter();
...
private:
Printer();
Printer(const Printer& rhs);
...
};
Printer& Printer::thePrinter(){
static Printer p;
return p;
}
thePrinter的实现使用了函数的static对象,与class的static对象相比,函数中的static对象保证在第一次调用该函数时才被初始化,这可以避免由于"C++没有规定定义于不同编译单元内的全局对象的初始化顺序"而导致的错误.
另外一种策略(以下称策略2)是当外界申请太多对象时,在构造函数内抛出异常:
class Printer {
public:
class TooManyObjects{};
Printer();
~Printer();
...
private:
static size_t numObjects;
Printer(const Printer& rhs);//由于只允许产生一个对象,所以不允许拷贝
};
size_t Printer::numObjects = 0;
Printer::Printer(){
if (numObjects >= 1) {
throw TooManyObjects();
}
proceed with normal construction here;
++numObjects;
}
Printer::~Printer(){
perform normal destruction here;
--numObjects;
}
策略2很容易被一般化,而且可以使对象数目限制在除1以外的值,但在涉及到类的继承以及内含的情况下不起作用,因为"Printer对象可于三种不同状态下生存:(1)它自己,(2)派生物的'base class'成分,(3) 内嵌于较大对象之中",例如:
涉及继承:
//彩色打印机类
class ColorPrinter: public Printer {
...
};
Printer p;
ColorPrinter cp; //抛出异常
涉及内含:
class CPFMachine {
private:
Printer p;
FaxMachine f;
CopyMachine c;
...
};
CPFMachine m1;
CPFMachine m2; //抛出异常
策略1不存在这个问题,因为将构造函数设为private实际上禁止了继承和内含的发生.由此启发,要禁止类被继承,可以将构造函数设为private,然后通过开放的接口提供对象引用或指针.
将两种策略结合,使其既有策略1的禁止被继承和内含的特点,又具有策略2的一般化特性,像这样:
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);
};
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);
}
3. 一个用来计算对象个数的Base Classes
可以将2中的策略1和策略2的结合一般化,将它抽象为一个类模板,任何需要限制对象数目的类只要继承这个模板的实例化即可:
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;
}
将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);
};
由于Counted<Printer>是Printer的基类,因此构造Printer之前一定会发生Counted<Printer>的构造,这样就把限制对象个数的任务交由基类Counted<Printer>来完成.此外,由于Printer只继承Counted的实现而不继承接口,因此使用private继承.如果要使开放Counted的部分接口,可以使用using declaration:
class Printer: private Counted<Printer> {
public:
...
using Counted<Printer>::objectCount;
...
};
或旧式的访问声明语法:
class Printer: private Counted<Printer> {
public:
...
Counted<Printer>::objectCount;
...
};
最后一个需要注意的地方是,static成员的定义问题,由于Counted类模板使用了static成员,因此必须要在类外定义,对于numObjects可以在头文件中初始化为0,但用于限制对象个数的maxObjects只能由用户定义并初始化,如果用户忘记定义,链接时将会报错并提醒.