🦄个人主页 :修修修也
🎏所属专栏 :C++
⚙️操作环境 :Visual Studio 2022

目录
在日常的开发中, 我们可能会遇到以下几种类的设计需求, 比如我们要求这个类不能被拷贝, 又或者这个类只能在堆/栈上创建对象等等。下面为大家介绍一下这些类的设计思路:
📌不能被拷贝的类
设计思路
拷贝只会发生在两个场景中: 拷贝构造函数以及赋值运算符重载, 因此想让一个类禁止拷贝, 只需要让该类不能调用拷贝构造函数以及赋值运算符重载即可。
具体实现
C++98版:
首先我们要知道, 拷贝构造和赋值运算符重载如果我们自己不手动实现, 那么系统是会默认给自动生成的, 为了防止系统自动生成的导致可以使用构造函数和赋值运算符重载, 所以我们要自己手动实现后再禁用。
具体做法就是: 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
原因:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
具体实现代码:
cppclass CopyBan { public: CopyBan(int val) :a(val) {} //... private: CopyBan(const CopyBan&); CopyBan& operator=(const CopyBan&); int a; //... };
测试结果:可以看到, 禁用后该类确实无法被拷贝了
C++11版:C++11版思路和C++98一样, 只是C++11扩展了delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
具体实现代码:
cppclass CopyBan { public: CopyBan(int val) :a(val) {} CopyBan(const CopyBan&) = delete; CopyBan& operator=(const CopyBan&) = delete; int a; };
测试结果: 可以看到删除后该类同样无法进行拷贝了
📌只能在堆上创建对象的类
设计思路
要让对象只能在堆上创建, 核心是要阻止栈/全局/静态对象的构造, 因此我们的思路1是:
将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
思路2是:
因为栈区/全局/静态的内存空间都是由操作系统自动管理的, 所以这三个地方创建的对象出了生命周期会自动调用析构函数, 那我们直接把析构函数私有化, 这样系统检测不到析构函数, 自然就不会允许在栈区/全局/静态区创建对象了。
具体实现
思路1实现代码:
cpp//只能在堆上创建 class HeapOnly { public: static HeapOnly* CreateObject(int val) { return new HeapOnly(val); } private: HeapOnly(int val) :a(val) {} HeapOnly(const HeapOnly&) = delete; int a; };
测试结果: 可以看到, 除了调用封装的固定Create接口在堆上创建对象, 其他任何形式想创建栈上的或全局的或静态的都不可以。
思路2实现代码:
cppclass HeapOnly { public: HeapOnly(int val) :a(val) {} void Destory(){ delete this; } private: ~HeapOnly() {} int a; };
测试结果:
📌只能在栈上创建对象的类
设计思路
要让对象只能在栈上创建, 核心是要禁用堆内存分配操作,同时允许栈内存分配, 因此我们的思路是:
将类的构造函数私有,禁用operator new和operator delete函数。防止别人调用在堆上生成对象。
提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建并返回给外部。
具体实现
实现代码:
cpp//只能在栈上创建 class StackOnly { public: static StackOnly CreateObject(int val) { StackOnly st(val); return st; } void* operator new(size_t size) = delete; void operator delete(void* p) = delete; private: StackOnly(int val) :a(val) {} int a; };
测试结果:
📌不能被继承的类
设计思路
C++98实现思路:
将基类构造函数私有化, 因为规定派生类初始化时必须调用基类的构造函数, 那将基类构造函数私有化之后, 派生类无法访问基类构造函数, 于是也无法构造对象, 故无法继承。
C++11实现思路:
使用final关键字, final关键字修饰的类无法被继承。
具体实现
c++98实现:
cpp//不能被继承的类 class NonInherit { public: static NonInherit GetInstance() { return NonInherit(); } private: NonInherit() {} };
测试结果: 果然派生类无法正常使用构造函数创建对象:
C++11实现:
cppclass NonInherit final { public: NonInherit() {} };
测试结果:
📌只能创建一个对象的类(单例模式)
什么是设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
什么是单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
单例模式的设计思想首先就是将构造函数私有化, 外部不能够随意创建实例, 只能通过给定的接口去拿到那个唯一的实例, 而这个唯一的实例是通过静态成员来实现的。
就比如我们日常开发中经常使用到的日志类, 就是一个很适合使用单例模式来实现的类。即全局只需要一个log对象, 后续有任何日志信息需要输出, 就直接调用那一个唯一的全局log对象即可。针对于唯一实例创建的时机不同, 单例模式有两种实现模式, 饿汉模式和懒汉模式, 下面分别为大家介绍一下:
饿汉模式
饿汉模式, 顾名思义就是我有食物我就去吃。对应到单例模式的设计里, 就是我不管后续会不会用到这个单例, 程序启动时就去把这个唯一的实例对象创建出来, 这是饿汉模式对于单例创建的一个设计思想。
具体到实际代码的实现上, 我们就选择将静态成员声明在类里, 直接在全局定义,这样程序一启动这个实例就会被初始化。
注意, 饿汉模式思想的优点是: 实现比较简单, 不需要考虑多线程中单例模式的安全问题。
但饿汉模式思想的缺点是: 如果这个实例创建后没有被使用, 并且这个实例自身占用了非常多的资源的话, 那么将会大大拖慢程序的启动速度, 从而导致程序性能下降。并且如果有两个单例类A和B, B是依赖A实例化的, 那么饿汉模式是无法保证它们的实例化顺序的, 这样就可能会导致实例化失败, 同样也是饿汉模式的一大无法避免的问题。
实现代码:
cpp//饿汉模式 class EagerSingleton { public: static EagerSingleton& getInstance() { return _instance; // 直接返回已初始化的静态实例 } void doSomething() { /* 操作单例对象的业务方法 */ } // 禁用拷贝和赋值保证单例特性 EagerSingleton(const EagerSingleton&) = delete; EagerSingleton& operator=(const EagerSingleton&) = delete; private: EagerSingleton() = default; // 私有构造函数 ~EagerSingleton() = default; static EagerSingleton _instance; // 静态成员声明 }; // 静态成员类外定义 EagerSingleton EagerSingleton::_instance; int main() { EagerSingleton::getInstance().doSomething(); return 0; }
懒汉模式
懒汉模式, 顾名思义就是我饿了我再去吃。对应到单例模式的设计里, 就是我后续用到这个单例了, 我再去创建这个唯一的实例对象。这就是懒汉模式对于单例创建的一个设计思想。
具体到实际代码的实现上,我们选择将静态成员设置为一个单例对象指针, 然后在get函数里再去实现如果第一次被调用就new一个实例出来, 从而做到了使用时实例化。
注意, 懒汉模式思想的优点是: 不会拖慢程序的启动速度, 只有有需要的时候才会去初始化, 所以程序性能会比饿汉模式要高。并且因为是根据需要初始化的, 所以可以保证不同单例初始化的前后顺序。
但饿汉模式思想的缺点 是: 实现比较难, 需要考虑多线程中创建单例模式的安全问题。
常规实现代码:
cpp//懒汉模式 class LazySingleton { public: static LazySingleton& getInstance() { if (_pinstance == nullptr)//双重检查, 只有第一次创建实例时才加锁 { unique_lock<mutex> lock(_mtx); if (_pinstance == nullptr)//如果是第一次调用,则创建实例 { _pinstance = new LazySingleton; } } return *_pinstance; // 返回已初始化的静态实例 } //防止需要显示调用释放单例 static void DelInstance() { if (_pinstance) { delete _pinstance; _pinstance = nullptr; } } void doSomething() { /* 操作单例对象的业务方法 */} // 禁用拷贝和赋值保证单例特性 LazySingleton(const LazySingleton&) = delete; LazySingleton& operator=(const LazySingleton&) = delete; private: LazySingleton() = default; // 私有构造函数 ~LazySingleton() = default; static LazySingleton* _pinstance; // 静态成员声明 static mutex _mtx; }; // 静态成员类外定义 LazySingleton* LazySingleton::_pinstance;
C++11之后的编译器保证局部静态变量的初始化是线程安全的, 极简实现代码:
cpp//懒汉模式 class LazySingleton { public: static LazySingleton& getInstance() { //局部的静态对象, 是在第一次调用时初始化 //C++11之前这里不是线程安全, C++11之后可以保证局部静态对象的初始化是线程安全的 static LazySingleton inst; return inst; } void doSomething() { /* 操作单例对象的业务方法 */} // 禁用拷贝和赋值保证单例特性 LazySingleton(const LazySingleton&) = delete; LazySingleton& operator=(const LazySingleton&) = delete; private: LazySingleton() = default; // 私有构造函数 ~LazySingleton() = default; };
结语
希望这篇关于 特殊类设计 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!
相关文章推荐
