[C++实现 设计模式(1)] : 单例模式

    xiaoxiao2024-11-09  94

    文章目录

    单例模式的定义单例模式的分类饿汉单例模式懒汉单例模式这是一种线程不安全的单例解决方法 单例模式的应用单例模式的优点单例模式的缺点单例模式的使用场景单例模式的注意事项单例模式的扩展

    单例模式的定义

    定义 : 确保某一个类只有一个实例 , 而且自行实例化并向整个系统提供这个实例 . 单例模式应该是23种设计模式中最简单的一种模式了 . 它有以下几个要素:

    私有的构造方法指向自己实例的私有静态引用以自己实例为返回值的静态的公有的方法

    UML图 :

    单例模式的分类

    单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例模式,一种是懒汉式单例模式 .

    饿汉单例模式

    定义 : 即类产生的时候就创建好实例对象,这是一种空间换时间的方式; 代码如下:

    //饿汉单例模式 #if 0 class Singleton { public: //获取唯一可用的对象 static Singleton* getInstance() { return &singleton; } //类中其他方法 , 尽量是static static void doSomething() { cout<<"我就是皇帝某某某....."<<endl; } private : //单例对象在这里 static Singleton singleton ; //让构造函数为 private,这样该类就不会被实例化 , 限制产生多个对象 Singleton() {cout<<"单例对象创建"<<endl;} ~Singleton() {cout<<"单例对象销毁"<<endl;} }; //静态数据成员初始化 , 分配内存 //<数据类型><类名>::<静态数据成员名>=<值> Singleton Singleton::singleton ; int main() { Singleton *p1 = Singleton::getInstance(); Singleton *p2 = Singleton::getInstance(); Singleton *p3 = Singleton::getInstance(); //只有一个地址 cout<<"p1: "<<p1<<endl<<"p2: "<<p2<<endl<<"p3: "<<p3<<endl; return 0; } #endif

    运行结果:

    饿汉单例模式是线程安全的,不需要考虑线程同步!

    懒汉单例模式

    定义 : 即在需要的时候,才创建对象,这是一种时间换空间的方式 ; 注 : 这是一种线程不安全的单例 . 我们后面来说. 代码如下 :

    //懒汉单例模式 #if 0 //线程不安全的单例 class Singleton { public: //注意这里和饿汉模式不同 static Singleton* getInstance() { if(singleton == nullptr) { singleton = new Singleton(); } return singleton; } static void doSomething() { cout<<"我就是皇帝某某某....."<<endl; } private : //注意这里 , 和饿汉模式声明的成员不同 static Singleton* singleton ; Singleton() {cout<<"单例对象创建"<<endl;} ~Singleton() {cout<<"单例对象销毁"<<endl;} //定义一个内部类 //如果单例模式的类中申请了其他资源,就无法释放,导致内存泄漏! //原因:此时全局数据区中,存储的并不是一个实例对象,而是一个实例对象的指针,即一个地址变量而已! //实例对象呢?在堆区,因为是通过new得来的!虽然这样能够减小全局数据区的占用,把实例对象这一大坨都放到堆区。 //可是!如何释放资源呢? //定义内部类 class CGarbo { public: CGarbo() {} ~CGarbo() { if(singleton != nullptr) { delete singleton; singleton = nullptr; } } }; // 定义一个内部类的静态对象 // 当该对象销毁时,顺带就释放singleton指向的堆区资源 static CGarbo m_garbo; }; Singleton* Singleton::singleton = nullptr; Singleton::CGarbo Singleton::m_garbo; int main() { Singleton *p1 = Singleton::getInstance(); Singleton *p2 = Singleton::getInstance(); Singleton *p3 = Singleton::getInstance(); cout<<"p1: "<<p1<<endl<<"p2: "<<p2<<endl<<"p3: "<<p3<<endl; return 0; } #endif

    运行结果:

           在这里解释一下为什么使用内部类 , 这是因为如果在类A(如 : Singleton)的析构函数中delete 类A的对象 , delete 还会调用类A的析构函数 , 这样就造成了一个死循环 . 所以要采用内部类的方式进行析构 , 防止内存的泄漏 .

           饿汉单例模式也可以写成这种形式 , 将static Singleton singleton ; 改为 static Singleton* singleton ; , 这里也需要使用内部类 .初始化的方式和懒汉单例模式一样.

    这是一种线程不安全的单例

    我们再来说说它为什么是线程不安全的单例.        该单例在低并发的情况下尚不会出现问题 , 若系统压力增大 , 并发量增加时则可能在内存中出现多个实例 , 破坏了最初的预期 . 为什么会出现这种情况呢?如一个线程A执行到 singleton = new Singleton(); , 但还没有获得对象(对象的初始化是需要时间的) , 第二个线程B也在执行 , 执行到if(singleton == nullptr) 判断 , 那么线程B获得的判断条件也是为真 , 于是继续运行下去 , 线程A获得了一个对象 , 线程B也获得了一个对象 , 在内存中就出现两个对象 !

    解决方法

    可以通过加锁的方式来解决.

    static Singleton* getInstance() { lock(); //伪代码 , 具体实现请查阅资料 if(singleton == nullptr) { singleton = new Singleton(); } unlock(); //伪代码 return singleton; }

    单例模式的应用

    单例模式的优点

    在内存中只有一个实例(对象) , 节省内存空间 .避免频繁的创建销毁对象,可以提高性能 .避免对共享资源的多重占用 .可以全局访问 .

    单例模式的缺点

    单例模式一般没有接口 , 扩展很困难 .单例模式对测试是不利的 .单例模式与单一职责原则有冲突 .

    单例模式的使用场景

    需要频繁实例化然后销毁的对象 .创建对象时耗时过多或者耗资源过多,但又经常用到的对象 .在整个项目中需要一个共享访问点或共享数据 .频繁访问数据库或文件的对象 .要求生成唯一序列号的环境 .需要定义大量的静态常量和静态方法(如工具类)的环境 .

    单例模式的注意事项

    在高并发的情况下 , 注意单例模式的线程同步问题 .只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象 .不要做断开单例类对象与类中静态引用的危险操作 .

    单例模式的扩展

           如果一个类可以产生多个对象,对象的数量不受限制,则是非常容易实现的,直接使用new关键字就可以了,如果只需要一个对象,使用单例模式就可以了,但是如果要求一个类只 能产生两三个对象呢?该怎么实现?

           这种需要产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例 , 便系统进行扩展 , 修正单例可能存在的性能问题,提供系统的响应速度 .例如读取文件 , 我们可以在系统启动时完成初始化工作 , 在内存中启动固定数量的reader实例 , 然后在需要读取文件时就可以快速相应 .

    最新回复(0)