1. 特殊类设计
普通类的设计基础上,提出一些限制条件设计的类就是特殊类。
1.1 不能被拷贝的类
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载。因此禁止拷贝的类只需要让该类禁止调用拷贝构造函数和赋值运算符重载函数即可。
C++98中的方式:将拷贝构造函数和赋值运算符重载函数只声明不定义,并设置成私有:
class CopyBan { public: CopyBan() {} private: CopyBan(const CopyBan& cb); // 拷贝构造函数声明 CopyBan& operator=(const CopyBan& cb); // 赋值运算符重载声明 };
原因:
- ① 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
- ② 只声明不定义:在调用拷贝构造和赋值运算符重载函数的时候,由于没有定义就会产生链接错误,在编译阶段就报错。不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
如上图代码,在对这个特殊类进行拷贝和赋值的时候,因为这两个成员函数私有而无法调用。
C++11的方式:C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 = delete,表示让编译器删除掉该默认成员函数。
class CopyBan { public: CopyBan() {} private: CopyBan(const CopyBan&) = delete; CopyBan& operator=(const CopyBan&) = delete; };
使用C++11中的给delete新赋予的意义来禁止生产拷贝构造和赋值运算符重载函数。
此时编译器也不会自动生成默认的拷贝构造函数和赋值运算符重载函数。
1.2 只能在堆上创建的类
正常创建类对象时,会在栈上创建,并且自动调用构造函数来初始化。
只能在创建在堆上时,就需要让该对象只能通过new来创建,并且调用构造函数来初始化。
class HeapOnly { public: static HeapOnly* CreateObject() { return new HeapOnly; } HeapOnly(const HeapOnly& hp) = delete;//禁止拷贝 HeapOnly& operator = (const HeapOnly& hp) = delete; // 禁止拷贝 private: HeapOnly() //构造函数 {} };
定义一个静态成员函数,在该函数内部new一个HeapOnly对象。将构造函数私有,并且禁止生成拷贝构造函数。
使用静态成员函数new一个HeapOnly对象的原因:
非静态成员函数在调用的时候,必须使用点(.)操作符来调用,这一步是为了传this指针。这样的前提是先有一个HeapOnly对象,但是构造函数设置成了私有,就无法创建这样一个对象。
而静态成员函数的调用不用传this指针,也就不用必须有HeapOnly对象,只需要类域::静态成员函数即可。否则就面临了先有鸡还是先有蛋的问题:非静态成员函数调用需要传对象,此时对象又只能调用非静态成员函数创建。静态成员函数属于HeapOnly域内,所以在new一个对象的时候,可以调用私有的构造函数。
禁止调用拷贝构造函数,并且私有化的原因:
这样的目的是为了禁止拷贝,防止使用堆区上的HeapOnly对象在栈区上拷贝,如下面代码:
而禁止了拷贝构造就杜绝了这一行为,从而保证了HeapOnly对象只能在堆上创建。
1.3 只能在栈上创建的类
只能主要做到不能在堆上创建类对象。
new一个对象的时候,会调用该类的operator new(size_t size)函数,在释放资源的时候又会调用该类的operator delete(void* p)函数。
StackOnly1 st1; static StackOnly1 st2; StackOnly1* st3 = new StackOnly1;
此时就是想办法禁止下面两行代码的使用了
方法1:通过一个静态成员函数在栈区上创建一个类对象,并且将默认构造函数私有化。
class StackOnly1 { public: static StackOnly1 CreateObject() { return StackOnly1(); } private: StackOnly1() {} };
此时new一个对象的时候,由于默认构造函数私有无法调用,所以报错。
但是此时我可以调用拷贝构造来在栈上创建:
如果加上防拷贝,第一种创建都创建不了了:
所以并没有很好的解决方式。
方法2:防止在堆上创建类对象就是要禁止调用这两个函数。
class StackOnly2 { public: StackOnly2() //构造函数 {} void* operator new(size_t size) = delete; // 禁止调用new void operator delete(void* p) = delete; // 禁止调用delete };
使用delete来禁止这两个函数的调用,那么在new一个对象的时候,就会产生编译错误,从而无法在堆区上创建类对象。此时在堆上创建对象时就会报错,尝试引用已经删除的函数。
这俩种设计方法共同的一个漏洞,类对象可以在静态区(数据段)上创建:
所以设计只能在栈上创建的类并没有什么很好的方案。
设计特殊类的核心点:只能通过静态成员函数来创建类,封掉其他所有创建方式。
1.4 不能被继承的类
C++98的方式:基类的构造函数私有,派生类在创建对象的时候,无法调用基类的构造函数
class NonInherit // 基类 { private: NonInherit() //基类构造函数私有 {} }; class B : public NonInherit // 派生类 {};
C++11的方式:使用C++11中的 final 关键字修饰基类,这个基类就无法继承。
此时不实例化也报错了。
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(中):https://developer.aliyun.com/article/1522501