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

    xiaoxiao2021-08-27  75

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

    假设定义一个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客户端说明了需求,他们会回去加上这个必须的初始化。 

    转载请注明原文地址: https://ju.6miu.com/read-677226.html

    最新回复(0)