• 【C++ techniques】限制某个class所能产生的对象数量


    允许零个或一个对象

    • 每当即将产生一个对象,就会有一个constructor被调用;
    • “阻止某个class产出对象”的最简单方法:
      将其constructorsh声明为private
    class CantBeInstantiated
    {
    private:
    	CantBeInstantiated();
    	CantBeInstantiated(const CantBeInstantiated&);
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    以上设计有3个成分:
    1. Printer类的构造函数为private,这样可以压制对象的产生;
    2. 全局函数thePrinter被声明为此类的一个友元函数,致使thePrinter不受私有构造函数的约束;
    3. thePrinter内含一个静态的Printer对象,意思是只有一个Printer对象会被产生出来。

    //一旦client需要和系统中唯一那台打印机打交道,就调用thePrinter
    //返回一个reference,代表一个Printer对象
    //所以thePrinter可以用在任何需要Printer对象的地方:
    
    class PrintJob
    {
    public:
    	PrintJob(const string& whatToPrint);
    	...
    };
     
    string buffer;
    ...				//把数据放进buffer内
    thePrinter.reset();
    thePrinter.submitJob(buffer);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    //将thePrinter设置成Printer的一个static member function之后:
    
    class Printer
    {
    public:
    	static Printer& thePrinter();
    	...
    private:
    	Printer();
    	Printer(const Printer& rhs);
    	...
    };
     
    PrintJob& Printer::thePrinter()
    {
    	static Printer p;
    	return p;
    }
    
    Printer::thePrinter().reset();
    Printer::thePrinter().submitJob(buffer);
    
    //或者
    using PrintingStuff::thePrinter;
    thePrinter().reset();
    thePrinter().submitJob(buffer);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在此thePrinter的实现代码中,有两个精细的地方值得探讨:
    1. 形成唯一一个Printer对象是函数中static对象而非class中static对象:

    • C++的一个哲学基础是,你不应该为你不适用的东西付出任何代价
    • “class拥有一个static对象”的意思:
      即使从未被用到,它也会被构造(及析构);
    • ”函数拥有一个static对象“的意思:
      此对象在函数第一次被调用时才产生,如果该函数从未被调用,这个对象也就绝不会诞生(然而你必须付出代价,在函数每次被调用时检查对象是否需要诞生)。
    • C++对于“同一编译单元内的statics”初始化顺序是有提出一些保证的,但对于“不同编译单元内的statics”的初始化顺序没有任何说明;
    • 一个function static的初始化时机:在函数第一次被调用,并且在该static被定义处;
    • 一个class static(或者global static)则不一定在什么时候初始化。

    2. 函数的“static对象与inline的互动”:

    • inline在概念上意味着编译器应该将每一个调用以函数本身取代;
    • non-member functions意味着这个函数有内部链接;程序的目标代码可能会对带有内部链接的函数复制一份以上的代码,也包括复制函数的static对象。

    现在的想法是,利用numObjects来追踪目前存在多少个Printer对象。这个数值将在constructor中累加,并在destructor中递减。如果外界企图构造太多的Printer对象,我们就抛出一个类型为TooManyObjects的exception:

    size_t Printer::numObjects = 0;
     
    Printer::Printer()
    {
    	if(numObjects >= 1)
    		throw TooManyObjects();
    	proceed with normal construction here;
    	++numObjects;
    }
    
    Printer::~Printer()
    {
    	perform normal desturction here;
    	--numObjects;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    不同的对象构造状态

    //设计一台彩色打印机,和一般打印机有许多共同点,所以使用继承机制
    class ColorPrinter:public Printer
    {
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    Printer p;
    ColorPrinter cp;
    
    • 1
    • 2
    class CPFMachine   //针对那些可影印、可打印、可传真的机器
    {
    private:
    	Printer p;		//针对打印
    	FaxMachine f;	//针对传真
    	CopyMachine c;	//针对影印
    	...
    };
    CPFMachine m1;	//没问题
    CPFMachine m2;	//抛出一个TooManyObjects exception。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    问题出在Printer对象可于3种不同状态下生存:
    1. 它自己;
    2. 派生物的“base class 成分”;
    3. 内嵌于较大对象之中。

    这些不同状态的呈现把”追踪目前存在的对象个数“的意义严重弄混了。

    class FSA
    {
    public:
    	//pseudo(伪)constructor
    	static FSA* makeFSA();
    	static FSA* makeFSA(const FSA& rhs);
    	...
    private:
    	FSA();
    	FSA(const FSA& rhs);
    	...
    };
     
    FSA* FSA::makeFSA()
    {
    	return new FSA();
    }
     
    FSA* FSA::makeFSA(const FSA& rhs)
    {
    	return new FSA(rhs);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    不像thePrinter函数总是返回一个reference代表唯一对象,这里的每一个makeFSA pseudo-constructor都返回一个指针,指向独一无二的对象。即允许产生无限个数的FSA对象。

    但是每一个pseudo~constructor都调用了new,意味调用者必须记得调用delete,否则就会出现资源泄漏的问题。如果希望delete自动被调用,也可以使用auto_ptr,见【C++ Exceptions】利用destructors避免泄露资源

    //间接调用default FSA constructor
    auto_ptr<FSA> pfsal(FSA::makeFSA());
    //间接调用FSA copy constructor
    auto_ptr<FSA> pfsa2()FSA::makeFSA(*pfsa1);
    
    • 1
    • 2
    • 3
    • 4

    允许对象生生灭灭

    将对象计数(object-counting)码和伪构造函数(pseudo-constructor)结合起来:

    class Printer
    {
    public:
    	class TooManyObjects{};
    	
    	//pseudo-constructor
    	static Printer* makePrinter();
    	~Printer();
    	void submitJob(const PrintJob& job);
    	void reset();
    	void performSelfTest();
    	...
     
    private:
    	static size_t numObjects;
    	Printer();
    	Printer(const Printer& rhs);	//不要定义此函数,我们决不允许复制行为
    };
     
    size_t Printer::numObjects = 0;
     
    Printer::Printer()
    {
    	if(numObjects >= 1)
    		throw TooManyObjects();
    	proceed with normal object construction here;
    	++numObjects;
    }
     
    Printer* Printer::makePrinter()
    {
    	return new Printer;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    //Clients使用这个Printer class,就像使用任何其他class一样
    //只不过他们必须调用pseudo-constructor,取代真正的constructor:
    
    Printer p;								//错误!default ctor是private
    Printer* p2 = Printer::makePrinter();	//没问题,间接调用default ctor
     
    Printer* p3 = *p2;						//错误!copy ctor是private
     
    p2->performSelfTest();                  //所有其他函数都像平常一般被调用之
    p2->reset();
    ...
    delete p2;   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这项技术很容易被泛化为“任意个数(不止一个)的对象”。我们唯一要做的就是将原来写死的常量1改为class专属的一个数值,然后将复制对象的限制去掉。例如:

    //下面是修订后的Printer class,允许最多10个Printer对象同时存在
    class Printer
    {
    public:
    	class TooManyObjects{};
    	
    	//pseudo-constructor
    	static Printer* makePrinter();
    	static Printer* makePrinter(const Printer& rhs);
    	~Printer();
    	void submitJob(const PrintJob& job);
    	void reset();
    	void performSelfTest();
    	...
     
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    一个用来计算对象个数的Base Class

    我们可以完成一个base class,作为对象计数之用,并让诸如Printer之类的classes继承它。

    //令计数器为“template所产生的classes”内的一个static member:
    
    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();			//用来避免ctor代码重复出现
    };
     
    template<class BeingCounted>
    Counted<BeingCounted>::Counted()
    {
    	init();
    }
     
    template<class BeingCounted>
    Counted<BeingCounted>::Counted(const Counted<BeingCounted>&)
    {
    	init();
    }
     
    template<class BeingCounted>
    Counted<BeingCounted>::init()
    {
    	if(numObjects >= maxObjects) 
    		throw TooManyObjects();
    	++numObjects;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    //修改Pirnter class,让它运用Counted template:
    class Printer:private Counted<Printer>
    {
    public:
    	//pseudo-constructors
    	static Printer* makePrinter);
    	static Printer* makePrinter(const Printer& rhs);
    	~Printer();
    	using Counted<Printer>::objectCount; //让Printer用户访问objectCount
    	
    	void submitJob(const PrintJob& job);
    	void reset();
    	void performSelfTest();
    	...
    	using Counted<Printer>::objectCount;
    	using Counted<Printer>::TooManyObjects;
    	
    private:
    	Printer();
    	Printer(const Printer& rhs);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    【linux】Linux 查看内存使用情况的几种方法汇总
    互联网医院系统:数字化时代中医疗服务的未来
    【深蓝学院】手写VIO第8章--相机与IMU时间戳同步--作业
    Spring事务传播机制
    【原创】K8S环境下研发如何本地调试?kt-connect使用详解
    ProTable 本地保存列设置
    CSS基础选择器总结
    Nginx:负载均衡(策略讲解+配置举例)
    含氟废水处理工艺案例
    JAVA JSP javaweb小区物业管理系统源码 小区管理系统 jsp小区物业服务管理系统
  • 原文地址:https://blog.csdn.net/weixin_49347928/article/details/133584950