More Effective C++ 条款26 限制某个class所能产生的对象数量

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);
    ...
};
Printer& thePrinter(){
    static Printer p; 
    return p;
}
View Code

    当要使用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;
}
View Code

    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;
}
View Code

     策略2很容易被一般化,而且可以使对象数目限制在除1以外的值,但在涉及到类的继承以及内含的情况下不起作用,因为"Printer对象可于三种不同状态下生存:(1)它自己,(2)派生物的'base class'成分,(3) 内嵌于较大对象之中",例如:

    涉及继承:

//彩色打印机类
class ColorPrinter: public Printer {
    ...
};
Printer p;
ColorPrinter cp; //抛出异常
View Code

    涉及内含:

class CPFMachine { 
private:
    Printer p; 
    FaxMachine f; 
    CopyMachine c;
    ...
};
CPFMachine m1; 
CPFMachine m2; //抛出异常
View Code

    策略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); 
}
View Code

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;
}
View Code

    将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);
};
View Code

    由于Counted<Printer>是Printer的基类,因此构造Printer之前一定会发生Counted<Printer>的构造,这样就把限制对象个数的任务交由基类Counted<Printer>来完成.此外,由于Printer只继承Counted的实现而不继承接口,因此使用private继承.如果要使开放Counted的部分接口,可以使用using declaration:

class Printer: private Counted<Printer> {
public:
    ...
    using Counted<Printer>::objectCount;
    ...
};
View Code

    或旧式的访问声明语法:

class Printer: private Counted<Printer> {
public:
    ...
    Counted<Printer>::objectCount;
    ...
};
View Code

    最后一个需要注意的地方是,static成员的定义问题,由于Counted类模板使用了static成员,因此必须要在类外定义,对于numObjects可以在头文件中初始化为0,但用于限制对象个数的maxObjects只能由用户定义并初始化,如果用户忘记定义,链接时将会报错并提醒.

原文地址:https://www.cnblogs.com/reasno/p/4841418.html