博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【more effective c++读书笔记】【第5章】技术(2)——限制某个class所能产生的对象数量...
阅读量:5298 次
发布时间:2019-06-14

本文共 7786 字,大约阅读时间需要 25 分钟。

一、允许零个对象

每当产生一个对象时会有一个构造函数被调用,因此阻止某个类产出对象的最简单方法就是将该类的构造函数声明为private。

例子:

class CantBeInstantiated{private:	CantBeInstantiated();	CantBeInstantiated(const CantBeInstantiated&);	....};

二、允许一个对象

1、对于声明为private的构造函数,可以引入友元函数来进行访问,并利用静态成员变量保证对象的唯一性。

例子:

//Printer.h#ifndef PRINTER_H#define PRINTER_H#include
class Printer{public: friend Printer& thePrinter(); void print();private: Printer(); Printer(const Printer& rhs);};Printer::Printer(){ std::cout << "Printer()" << std::endl;}Printer::Printer(const Printer& rhs){ std::cout << "Printer(const Printer& rhs)" << std::endl;}void Printer::print(){ std::cout << "print..." << std::endl;}Printer& thePrinter(){ static Printer p; return p;}#endif//main.cpp#include "Printer.h"using namespace std;int main(){ thePrinter().print(); thePrinter().print(); system("pause"); return 0;}

运行结果:

从运行结果中可以看出构造函数只被调用了一次,所以只产生了一个对象。

上述程序的设计有3个成分:

第一,Printer class的constructor属性private,可以压制对象的产生;

第二,全局函数thePrinter被声明为class的一个friend,致使thePrinter不受private constructors的约束;

第三,thePrinter内含一个static Printer对象,意思是只有一个Printer对象会被产生出来。

2、对于声明为private的构造函数,可以引入静态成员函数来进行访问,并利用静态成员变量保证对象的唯一性。

例子:

//Printer.h#ifndef PRINTER_H#define PRINTER_H#include
class Printer{public: static Printer& thePrinter(); void print();private: Printer(); Printer(const Printer& rhs);};Printer::Printer(){ std::cout << "Printer()" << std::endl;}Printer::Printer(const Printer& rhs){ std::cout << "Printer(const Printer& rhs)" << std::endl;}void Printer::print(){ std::cout << "print..." << std::endl;}Printer& Printer::thePrinter(){ static Printer p; return p;}#endif//main.cpp#include "Printer.h"using namespace std;int main(){ Printer::thePrinter().print(); Printer::thePrinter().print(); system("pause"); return 0;}

运行结果:

上述两种实现思想是一致的,都是通过thePrint函数来完成客户端访问打印机的需求。thePrinter函数的实现有两点值得探讨:

第一,形成唯一一个Printer对象的是函数中的静态对象而不是类中的静态成员。类中的静态对象有两个缺点,一个总是被构造(及析构),即使不使用该对象;另一个缺点是它的初始化时间不确定。而函数拥有一个静态对象的意思是此对象在函数第一次调用时才产生,如果该函数从未被调用,这个对象就绝不会产生。函数内的静态对象的初始化时机是在该函数第一次被调用时,并且在该静态对象被定义处。

第二、thePrinter函数没有声明为内联函数,因为内联意味编译器用函数本体代替对函数的每一个调用,这对于非成员函数来说有内部连接。而函数如果有内部连接,可能在程序中被复制,可能会使程序有多份该函数的静态对象的副本。

三、允许多个对象

1、引入计数器简单地计算目前存在的对象个数,并当外界申请太多对象时,在构造函数内抛出一个异常。

例子:

//Printer.h#ifndef PRINTER_H#define PRINTER_H#include
class Printer{public: class TooManyObjects{};//当外界申请太多对象时,抛出这种异常类 Printer(); ~Printer(); void print();private: static size_t numObjects; Printer(const Printer& rhs);//这里限制个数为1,所以不允许复制};size_t Printer::numObjects = 0;Printer::Printer(){ try{ if (numObjects >= 1)//这里限制个数为1,可根据需要改 throw TooManyObjects(); } catch (TooManyObjects& e){ std::cout << "对象个数超过限制" << std::endl; exit(1); } std::cout << "Printer()" << std::endl; ++numObjects;}Printer::~Printer(){ std::cout << "~Printer()" << std::endl; --numObjects;}Printer::Printer(const Printer& rhs){ std::cout << "Printer(const Printer& rhs)" << std::endl;}void Printer::print(){ std::cout << "print..." << std::endl;}#endif//main.cpp#include "Printer.h"using namespace std;int main(){ Printer printer1; printer1.print(); //Printer printer2;//导致程序中断 //printer2.print(); system("pause"); return 0;}

上述方法也会出现问题,因为Printer对象能存在于三种不同状态下:1)它自己,2)派生类的基类,3)内嵌于较大的对象之中。

例子:

class ColorPrinter :public Printer{	...};Printer p;ColorPrinter cp;

上述例子定义了两个Printer对象,一个是p,一个是cp的Printer成分。一旦执行,在cp的基类成分构造时,会有一个TooManyObjects exception被抛出,这可能不是程序员想要的。

四、出现的问题及改进方案

利用thePrinter函数将Printer对象的数量限制为1,却也限制我们在每次执行程序时只能有唯一一个Printer对象。因此我们不可能写出这样的代码:

建立Printer对象p1;

使用p1;

释放p1;

建立Printer对象p2;

使用p2;

释放p2;

....

我们可以将对象计数和伪构造函数结合起来解决上述问题。

例子:

//Printer.h#ifndef PRINTER_H#define PRINTER_H#include
class Printer{public: class TooManyObjects{};//当外界申请太多对象时,抛出这种异常类 static Printer* makePrinter(); ~Printer(); void print();private: static size_t numObjects; Printer(); Printer(const Printer& rhs);//不要定义此函数,因为不希望允许复制行为};size_t Printer::numObjects = 0;Printer::Printer(){ try{ if (numObjects >= 1)//这里限制个数为1,可根据需要改 throw TooManyObjects(); } catch (TooManyObjects& e){ std::cout << "对象个数超过限制" << std::endl; exit(1); } std::cout << "Printer()" << std::endl; ++numObjects;}Printer* Printer::makePrinter(){ return new Printer;}Printer::~Printer(){ std::cout << "~Printer()" << std::endl; --numObjects;}void Printer::print(){ std::cout << "print..." << std::endl;}#endif//main.cpp#include "Printer.h"using namespace std;int main(){ //Printer p1;//错误,默认构造函数是private Printer* p2 = Printer::makePrinter(); p2->print(); //Printer* p3 = p2;//错误,拷贝构造函数是private且没定义 delete p2; Printer* p4 = Printer::makePrinter(); p4->print(); delete p4; system("pause"); return 0;}

可将上述例子泛化为任意个数的对象,只需将原来的1改成其他数值,然后将复制对象的限制去掉。

例子:

//Printer.h#ifndef PRINTER_H#define PRINTER_H#include
class Printer{public: class TooManyObjects{};//当外界申请太多对象时,抛出这种异常类 static Printer* makePrinter(); static Printer* makePrinter(const Printer& rhs); ~Printer(); void print();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(){ try{ if (numObjects >= maxObjects) throw TooManyObjects(); } catch (TooManyObjects& e){ std::cout << "对象个数超过限制" << std::endl; exit(1); } std::cout << "Printer()" << std::endl; ++numObjects;}Printer::Printer(const Printer& rhs){ try{ if (numObjects >= maxObjects) throw TooManyObjects(); } catch (TooManyObjects& e){ std::cout << "对象个数超过限制" << std::endl; exit(1); } std::cout << "Printer(const Printer& rhs)" << std::endl; ++numObjects;}Printer* Printer::makePrinter(){ return new Printer;}Printer* Printer::makePrinter(const Printer& rhs){ return new Printer(rhs);}Printer::~Printer(){ std::cout << "~Printer()" << std::endl; --numObjects;}void Printer::print(){ std::cout << "print..." << std::endl;}#endif//main.cpp#include "Printer.h"using namespace std;int main(){ Printer* p1 = Printer::makePrinter(); p1->print(); Printer* p2 = Printer::makePrinter(*p1); p2->print(); delete p2; delete p1; system("pause"); return 0;}

上述方案基本上完善了,但是如果我们有大量像Printer需要限制对象数量的类,就必须为每一个类编写一样的代码,,应该避免这种重复性的工作,所以需要一个一个用来计算对象个数的基类。

五、一个用来计算对象个数的基类

//Counted.h#ifndef COUNTED_H#define COUNTED_Htemplate
class Counted{public: class TooManyObjects{}; static int objectCount(){ return numOfObjects; }protected: Counted(); Counted(const Counted& rhs); ~Counted(){ --numOfObjects; }private: static int numOfObjects; static const size_t maxObjects; void init();};template
Counted
::Counted(){ init();}template
Counted
::Counted(const Counted& rhs){ init();}template
void Counted
::init(){ if (numOfObjects >= maxObjects) throw TooManyObjects(); ++numOfObjects;}template
int Counted
::numOfObjects = 0;#endif//Printer.h#ifndef PRINTER_H#define PRINTER_H#include "Counted.h"#include
class Printer:private Counted
{public: static Printer* makePrinter(); static Printer* makePrinter(const Printer& rhs); ~Printer(); using Counted
::objectCount; using Counted
::TooManyObjects;private: Printer(); Printer(const Printer& rhs);};Printer::Printer(){ std::cout << "Printer()" << std::endl;}Printer::Printer(const Printer& rhs){ std::cout << "Printer(const Printer& rhs)" << std::endl;}Printer* Printer::makePrinter(){ return new Printer;}Printer* Printer::makePrinter(const Printer& rhs){ return new Printer(rhs);}Printer::~Printer(){ std::cout << "~Printer()" << std::endl;}const size_t Counted
::maxObjects = 10;#endif//main.cpp#include "Printer.h"using namespace std;int main(){ Printer* p1 = Printer::makePrinter(); Printer* p2 = Printer::makePrinter(); Printer* p3 = Printer::makePrinter(*p2); cout << Printer::objectCount() << endl; delete p3; delete p2; delete p1; system("pause"); return 0;}
 

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://www.cnblogs.com/ruan875417/p/4785419.html

你可能感兴趣的文章
C++ 删除字符串的两种实现方式
查看>>
电容选型
查看>>
ORA-01502: 索引'P_ABCD.PK_WEB_BASE'或这类索引的分区处于不可用状态
查看>>
Spring EL hello world实例
查看>>
百度地图API地理位置和坐标转换
查看>>
MyBatis学习总结(六)——调用存储过程
查看>>
code-代码平台服务器路径
查看>>
离线安装 Visual Studio Express 而不下载整个镜像文件的方法(转载)
查看>>
2017-2018-2偏微分方程复习题解析10
查看>>
Java抽象类和接口的比较
查看>>
web技术工具帖
查看>>
一次性搞明白 service和factory区别
查看>>
iOS UI控件5-UIPickerView
查看>>
深入Java虚拟机读书笔记第三章安全
查看>>
素数筛选法
查看>>
php连接postgresql数据库
查看>>
Visual studio之C# 调用系统软键盘(外部"osk.exe")
查看>>
移动应用开发选型:向左还是向右?
查看>>
开发进度一
查看>>
十天冲刺(6)
查看>>