《剑指offer》:[48]不能被继承的类-单例模式
2016-06-25 11:52
459 查看
题目:不能被继承的类
不能继承,一般我们会对构造函数做手脚。不能继承,继承会发生什么,继承的类在创建对象的时候,会自动调用父类的构造函数,如果我们在这里限制让子类不能调用父类的构造和析构就是实现了不能继承,但是也不能影响自己的使用。
方案一:思想:设置构造函数和析构函数为私有+新增两方法创建和销毁对象。
原因:
(1)把构造函数和析构函数设置为私有函数,这样可以防止子类调用构造函数和析构函数,这样也就实现了防止继承;
(2)新增两个方法来创建对象和销毁对象是为了不影响自己创建和销毁对象,不能为了限制别人把自己也坑了,这样就不划算了。所以我们采用了静态方法创建和销毁对象。
缺点:会影响类对象的创建,并且只能创建堆上的对象,不能创建栈上的对象(私有嘛)!
具体代码实现测试:
运行结果:
将函数及实例化指针设置为静态的主要有两点考虑:首先,类的静态成员变量就是指的类共享的对象,而单例模式的对象设成静态就是为了让该类所有成员共享同一个对象,所以从语义上是合适的;其次,从语法考虑,常见的单例模式都是通过一个静态方法(如getInstance)返回其单例,因为静态方法的内部不能直接使用非静态变量,所以返回的这个实例就是静态的。
方案二:思想:使用虚基类 和友元类处理(推荐,使用起来比较常规,堆和栈的对象都可以得到)。
方法:虚基类+虚基类构造函数和析构函数私有化+不能派生的SealClass变成基类友员类
(1)另外设置一个基类Base,并把基类Base的构造函数和析构函数设置为私有。
(2)把要设置SealClass类变成基类的友元类
(3)SealClass类虚拟继承Base。
原因:
a、把基类Base的构造函数和析构函数设置为私有化,主要目的也是为了防止让其他类从基类base中派生,当然此时该类也不能定义对象;
b、设置SealClass类为基类Base的友元类,主要目的是根据友员的性质,该类可以自由访问基类的私有的构造函数和析构函数;
c、基类的友员不能被派生类继承,此时之后创建的Driver就不能使用父类的友元了。
d、为什么是虚函数?
主要原因:如果是虚继承的话,假设类Driver要从SealClass继承,由于是虚函数,该派生类Driver会直接调用虚基类Base的构造函数,而不会通过基类SealClass,此时就会报错。
虚继承还有一个优点:就是解决多继承的二义性问题,如果B和C都继承A,D继承B和C,如果D要用A的一个变量,就会出现二义性,是DBA还是DCA呢:
如果是虚继承,调用的时候就会越过BC,直接调用A的数据,解决了这种二义性的问题。
缺点:不易扩展,主要是因为编译器对虚基类和友元类friend类型的支持和要求不一样。
测试代码如下:
由上得来的模板类,只要是某个类不想派生出子类,就可以使用此模板。
具体实现代码如下:
注意(1):一定要设置为虚继承:如果不是虚继承的话,类Driver的对象会调用父类的构造函数,而且父类SealClass可以调用Base的构造函数和析构函数,则此时是可以有类从SealClass继承的。
代码如下:
注意(2):在有虚继承的情况下, 派生类先调用虚基类的构造函数,再调用非虚基类的构造函数。虚基类的子对象在整个初始化过程中只调用一次。
例如:
与注意中的(1)不同,(1)中的private:Base,是由SealClass调用的,而这里是Driver直接先调用的基类的构造函数而来,结果一样但是过程不一样。
不能继承,一般我们会对构造函数做手脚。不能继承,继承会发生什么,继承的类在创建对象的时候,会自动调用父类的构造函数,如果我们在这里限制让子类不能调用父类的构造和析构就是实现了不能继承,但是也不能影响自己的使用。
方案一:思想:设置构造函数和析构函数为私有+新增两方法创建和销毁对象。
原因:
(1)把构造函数和析构函数设置为私有函数,这样可以防止子类调用构造函数和析构函数,这样也就实现了防止继承;
(2)新增两个方法来创建对象和销毁对象是为了不影响自己创建和销毁对象,不能为了限制别人把自己也坑了,这样就不划算了。所以我们采用了静态方法创建和销毁对象。
缺点:会影响类对象的创建,并且只能创建堆上的对象,不能创建栈上的对象(私有嘛)!
具体代码实现测试:
#include <iostream> using namespace std; class SealClass { public: static SealClass *GetInstance()//<span style="font-family: Arial; font-size: 14px; line-height: 26px;">静态的全局访问接口;</span> { if(NULL==m_pInstance) { m_pInstace = new SealClass(); } return m_pInstace; } static void DeleteInstance(SealClass *pInstance) { delete pInstance; } int GetNum() { return number; } private: static SealClass *m_pInstance; //<span style="font-family: Arial; font-size: 14px; line-height: 26px;">静态的私有实例化指针;</span> SealClass(){ } ~SealClass() //其实析构函数可以不用写到这里; { cout<<"private: ~SealClass!"<<endl; } }; class Driver:public SealClass { public: Driver(){ } }; int main() { //SealClass ss(11);//ERROR,不能访问私有的构造函数; //SealClass *s=new SealClass(11);//ERROR,不能访问私有的构造函数; SealClass *ss=SealClass::GetInstance(11); //OK,只能是堆上的对象; int result=ss->GetNum(); cout<<"number: "<<result<<endl; SealClass::DeleteInstance(ss); //创建子类: //Driver d; //ERROR,不能访问构造和析构函数; //Driver *d=new Driver;//ERROR,不能访问构造和析构函数; system("pause"); return 0; }
运行结果:
将函数及实例化指针设置为静态的主要有两点考虑:首先,类的静态成员变量就是指的类共享的对象,而单例模式的对象设成静态就是为了让该类所有成员共享同一个对象,所以从语义上是合适的;其次,从语法考虑,常见的单例模式都是通过一个静态方法(如getInstance)返回其单例,因为静态方法的内部不能直接使用非静态变量,所以返回的这个实例就是静态的。
方案二:思想:使用虚基类 和友元类处理(推荐,使用起来比较常规,堆和栈的对象都可以得到)。
方法:虚基类+虚基类构造函数和析构函数私有化+不能派生的SealClass变成基类友员类
(1)另外设置一个基类Base,并把基类Base的构造函数和析构函数设置为私有。
(2)把要设置SealClass类变成基类的友元类
(3)SealClass类虚拟继承Base。
原因:
a、把基类Base的构造函数和析构函数设置为私有化,主要目的也是为了防止让其他类从基类base中派生,当然此时该类也不能定义对象;
b、设置SealClass类为基类Base的友元类,主要目的是根据友员的性质,该类可以自由访问基类的私有的构造函数和析构函数;
c、基类的友员不能被派生类继承,此时之后创建的Driver就不能使用父类的友元了。
d、为什么是虚函数?
主要原因:如果是虚继承的话,假设类Driver要从SealClass继承,由于是虚函数,该派生类Driver会直接调用虚基类Base的构造函数,而不会通过基类SealClass,此时就会报错。
虚继承还有一个优点:就是解决多继承的二义性问题,如果B和C都继承A,D继承B和C,如果D要用A的一个变量,就会出现二义性,是DBA还是DCA呢:
如果是虚继承,调用的时候就会越过BC,直接调用A的数据,解决了这种二义性的问题。
缺点:不易扩展,主要是因为编译器对虚基类和友元类friend类型的支持和要求不一样。
测试代码如下:
#include <iostream> using namespace std; class Base { public: friend class SealClass; private: Base() { cout<<"private: Base!"<<endl; } ~Base() { cout<<"private: ~Base!"<<endl; } }; class SealClass:virtual public Base { public: SealClass() { cout<<"SealClass!"<<endl; } ~SealClass() { cout<<"~SealClass!"<<endl; } }; //class Driver:public SealClass //{ //public: // Driver() // { // cout<<"Driver!"<<endl; // } // ~Driver() // { // cout<<"~Driver!"<<endl; // } //}; int main() { //Driver d;//ERROR,不能访问私有的构造和析构: //Driver *d=new Driver;//ERROR,不能访问私有的构造和析构: SealClass s; //OK; SealClass *ss=new SealClass; //OK delete ss; system("pause"); return 0; }运行结果:
由上得来的模板类,只要是某个类不想派生出子类,就可以使用此模板。
具体实现代码如下:
template<typename T> class Base { public: friend T; private: Base(){} ~Base(){} }; class SealClass : virtual public Base<SealClass> { public: SealClass(){} ~SealClass(){} };
注意(1):一定要设置为虚继承:如果不是虚继承的话,类Driver的对象会调用父类的构造函数,而且父类SealClass可以调用Base的构造函数和析构函数,则此时是可以有类从SealClass继承的。
代码如下:
#include <iostream> using namespace std; class Base { public: friend class SealClass; private: Base() { cout<<"private: Base!"<<endl; } ~Base() { cout<<"private: ~Base!"<<endl; } }; class SealClass: public Base { public: SealClass() { cout<<"SealClass!"<<endl; } ~SealClass() { cout<<"~SealClass!"<<endl; } }; class Driver:public SealClass { public: Driver() { cout<<"Driver!"<<endl; } ~Driver() { cout<<"~Driver!"<<endl; } }; int main() { Driver d; system("pause"); return 0; }构造函数的顺序:
注意(2):在有虚继承的情况下, 派生类先调用虚基类的构造函数,再调用非虚基类的构造函数。虚基类的子对象在整个初始化过程中只调用一次。
例如:
#include <iostream> using namespace std; class Base { public: Base() { cout<<"private: Base!"<<endl; } ~Base() { cout<<"private: ~Base!"<<endl; } }; class SealClass: public Base { public: SealClass() { cout<<"SealClass!"<<endl; } ~SealClass() { cout<<"~SealClass!"<<endl; } }; class Driver:public SealClass { public: Driver() { cout<<"Driver!"<<endl; } ~Driver() { cout<<"~Driver!"<<endl; } }; int main() { Driver d; system("pause"); return 0; }运行结果:
与注意中的(1)不同,(1)中的private:Base,是由SealClass调用的,而这里是Driver直接先调用的基类的构造函数而来,结果一样但是过程不一样。
相关文章推荐
- 列表状态自动切换
- Java Web 开发遇到的坑和注意点 涉及Jquery,Servlet等等
- caffe— 使用模型进行fine tune
- 梅特卡夫法则(Metcalfe's law)
- 《剑指offer》:[47]不用加减乘除做加法
- ReactJS学习系列课程(React ES6语法使用)
- fullPage.js全屏滚动插件
- 《剑指offer》:[46]求1+2+3+...+n
- JS中关于把函数作为另一函数的参数的几点小总结
- 《剑指offer》:[45]圆圈中最后剩下的数字(约瑟夫(Josephuse)环问题)
- 在调试javascript的时候,要常使用alert()
- 《剑指offer》:[44]扑克牌的顺子
- 《剑指offer》:[43]N个骰子的点数
- 提高JavaScript性能
- CSS权威指南-伪元素选择器
- JavaScript 与html的元素产生关联
- javascript标准对象
- jquery实现上传文件大小类型的验证例子(推荐)
- String、StringBuffer类
- css块级元素居中