【C++】特殊类设计
🦄个人主页:修修修也
🎏所属专栏:C++
⚙️操作环境:Visual Studio 2022
目录
📌不能被拷贝的类
设计思路
具体实现
📌只能在堆上创建对象的类
设计思路
具体实现
📌只能在栈上创建对象的类
设计思路
具体实现
📌不能被继承的类
设计思路
具体实现
📌只能创建一个对象的类(单例模式)
什么是设计模式
什么是单例模式
饿汉模式
懒汉模式
结语
在日常的开发中, 我们可能会遇到以下几种类的设计需求, 比如我们要求这个类不能被拷贝, 又或者这个类只能在堆/栈上创建对象等等。下面为大家介绍一下这些类的设计思路:
📌不能被拷贝的类
设计思路
拷贝只会发生在两个场景中: 拷贝构造函数以及赋值运算符重载, 因此想让一个类禁止拷贝, 只需要让该类不能调用拷贝构造函数以及赋值运算符重载即可。
具体实现
C++98版:
首先我们要知道, 拷贝构造和赋值运算符重载如果我们自己不手动实现, 那么系统是会默认给自动生成的, 为了防止系统自动生成的导致可以使用构造函数和赋值运算符重载, 所以我们要自己手动实现后再禁用。
具体做法就是: 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
原因:
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。具体实现代码:
class 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,表示让编译器删除掉该默认成员函数。
具体实现代码:
class CopyBan { public:CopyBan(int val):a(val){}CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;int a; };
测试结果: 可以看到删除后该类同样无法进行拷贝了
📌只能在堆上创建对象的类
设计思路
要让对象只能在堆上创建, 核心是要阻止栈/全局/静态对象的构造, 因此我们的思路1是:
1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建思路2是:
因为栈区/全局/静态的内存空间都是由操作系统自动管理的, 所以这三个地方创建的对象出了生命周期会自动调用析构函数, 那我们直接把析构函数私有化, 这样系统检测不到析构函数, 自然就不会允许在栈区/全局/静态区创建对象了。
具体实现
思路1实现代码:
//只能在堆上创建 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实现代码:
class HeapOnly { public:HeapOnly(int val):a(val){}void Destory(){delete this;} private:~HeapOnly(){}int a; };
测试结果:
📌只能在栈上创建对象的类
设计思路
要让对象只能在栈上创建, 核心是要禁用堆内存分配操作,同时允许栈内存分配, 因此我们的思路是:
1. 将类的构造函数私有,禁用operator new和operator delete函数。防止别人调用在堆上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建并返回给外部。
具体实现
实现代码:
//只能在栈上创建 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实现:
//不能被继承的类 class NonInherit { public:static NonInherit GetInstance(){return NonInherit();} private:NonInherit(){} };
测试结果: 果然派生类无法正常使用构造函数创建对象:
C++11实现:
class NonInherit final { public:NonInherit(){} };
测试结果:
📌只能创建一个对象的类(单例模式)
什么是设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
什么是单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
单例模式的设计思想首先就是将构造函数私有化, 外部不能够随意创建实例, 只能通过给定的接口去拿到那个唯一的实例, 而这个唯一的实例是通过静态成员来实现的。
就比如我们日常开发中经常使用到的日志类, 就是一个很适合使用单例模式来实现的类。即全局只需要一个log对象, 后续有任何日志信息需要输出, 就直接调用那一个唯一的全局log对象即可。针对于唯一实例创建的时机不同, 单例模式有两种实现模式, 饿汉模式和懒汉模式, 下面分别为大家介绍一下:
饿汉模式
饿汉模式, 顾名思义就是我有食物我就去吃。对应到单例模式的设计里, 就是我不管后续会不会用到这个单例, 程序启动时就去把这个唯一的实例对象创建出来, 这是饿汉模式对于单例创建的一个设计思想。
具体到实际代码的实现上, 我们就选择将静态成员声明在类里, 直接在全局定义,这样程序一启动这个实例就会被初始化。
注意, 饿汉模式思想的优点是: 实现比较简单, 不需要考虑多线程中单例模式的安全问题。
但饿汉模式思想的缺点是: 如果这个实例创建后没有被使用, 并且这个实例自身占用了非常多的资源的话, 那么将会大大拖慢程序的启动速度, 从而导致程序性能下降。并且如果有两个单例类A和B, B是依赖A实例化的, 那么饿汉模式是无法保证它们的实例化顺序的, 这样就可能会导致实例化失败, 同样也是饿汉模式的一大无法避免的问题。
实现代码:
//饿汉模式 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一个实例出来, 从而做到了使用时实例化。
注意, 懒汉模式思想的优点是: 不会拖慢程序的启动速度, 只有有需要的时候才会去初始化, 所以程序性能会比饿汉模式要高。并且因为是根据需要初始化的, 所以可以保证不同单例初始化的前后顺序。
但饿汉模式思想的缺点是: 实现比较难, 需要考虑多线程中创建单例模式的安全问题。
常规实现代码:
//懒汉模式 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之后的编译器保证局部静态变量的初始化是线程安全的, 极简实现代码:
//懒汉模式 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; };
结语
希望这篇关于 特殊类设计 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!
相关文章推荐
【C++11】智能指针
【C++11】左值引用、右值引用、移动语义和完美转发
【C++】STL标准模板库容器set
【C++】模拟实现二叉搜索(排序)树
【C++】模拟实现priority_queue(优先级队列)
【C++】模拟实现queue
【C++】模拟实现stack
【C++】模拟实现list
【C++】模拟实现vector
【C++】标准库类型vector
【C++】模拟实现string类
【C++】标准库类型string
【C++】构建第一个C++类:Date类
【C++】类的六大默认成员函数及其特性(万字详解)
【C++】什么是类与对象?