• 总结C++单例模式


    单例模式介绍

    单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    单例模式有三个关键点:

    1、单例类只能有一个实例。
      为此,单例类只能提供私有的构造函数,即保证不能随意创建该类的实例。

    2、单例类必须自己创建自己的唯一实例。
      因为构造函数是私有的,其他对象不能创建单例类的实例,只能是单例类自己来创建。

    3、单例类必须给所有其他对象提供这一实例。
      外界需要获取并使用这个单例类的实例,但是由于该类的构造函数是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法创建或者获取它本身的静态私有对象并返回。

    单例类主要解决了一个全局使用的类的频繁的创建与销毁。

    所以单例模式有以下几个优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例;2、避免对资源的多重占用。

    那单例模式有一个不好的地方就是,单例类没有接口,不能继承。

    单例模式使用场景

    单例模式就是一个类只能被实例化一次 ,也就是只能有一个实例化的对象的类。减少每次都new对象的开销,节省系统资源,同时也保证了访问对象的唯一实例。常用于如下场景:
    1.频繁实例化然后销毁的对象。
    2.创建对象太耗时,消耗太多资源,但又经常用到。

    单例模式分为懒汉式和饿汉式两种。

    懒汉式

    懒汉式:故名思义,懒汉很懒只有饿了才会去找吃的。也就是说,只有在需要使用的时候才会去实例化。

    线程不安全的懒汉式

    代码:

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    
    //常规的懒汉式单例模式,存在线程安全问题
    class Singleton2 {
    public:
    	static Singleton2* getInstance() {
    		if (instance == NULL) {
    			instance = new Singleton2();
    		}
    		return instance;
    	}
    	void test() {
    		cout << "this is Singleton2..." << endl;
    	}
    
    private:
    	Singleton2() { cout << "Singleton2构造函数" << endl; };	//构造函数私有化
    	Singleton2(const Singleton2&) {}						//拷贝构造函数私有化
    	Singleton2& operator=(const Singleton2&) {}				//赋值运算符私有化
    	static Singleton2* instance;							//静态成员变量
    
    	//类中嵌套类,用于自动回收资源
    	class GC {
    	public:
    		~GC() {
    			if (instance != NULL) {
    				delete instance;
    				instance = NULL;
    				cout << "GC2自动回收..." << endl;
    			}
    			cout << "GC2..." << endl;
    		}
    	};
    
    	static GC gc;
    };
    //静态成员变量,类内定义,类外初始化
    Singleton2* Singleton2::instance = NULL;
    Singleton2::GC Singleton2::gc;
    
    int main() {
    	Singleton2::getInstance()->test();
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    上面的代码是线程不安全的单例模式。因为,如果两个线程同时获取单例类的实例,都发现实例不存在,因此都会进行实例化,就会产生两个实例都要赋值给instance。

    这里的线程安全指的是:一个线程在初始化某个变量的时候,其他线程执行到这个变量的初始化的时候,就会挂起。就是说只有一个线程会执行该变量的初始化

    结果:
    在这里插入图片描述

    为了解决上述问题,设计出线程安全的懒汉式单例模式,需要考虑加锁。当然也可以不加锁,使用静态局部变量。
    线程安全的懒汉式有两种实现方式,一种是使用静态局部变量,另一种是加锁。

    使用静态局部变量(C++11)

    由于C++11中规定静态成员变量就是线程安全的,所以这种写法不存在线程安全问题。
    原理就是函数的局部静态变量生命周期随着进程结束而结束。

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    
    //使用静态局部变量的懒汉式单例模式
    //由于C++11中规定静态成员变量就是线程安全的,所以这种写法不存在线程安全问题
    class Singleton1 {
    public:
    	static Singleton1* getInstance() {
    		static Singleton1 instance;
    		return &instance;
    	}
    
    	void test() {
    		cout << "this is Singleton1..." << endl;
    	}
    
    private:
    	Singleton1() { cout << "Singleton1构造函数" << endl; }								//构造函数私有化
    	Singleton1(const Singleton1&){}				//拷贝构造函数私有化
    	Singleton1& operator=(const Singleton1&) {}	//赋值运算符私有化
    };
    
    int main() {
    	Singleton1::getInstance()->test();	//ingleton1构造函数	  this is Singleton1...
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    结果:
    在这里插入图片描述

    加锁的懒汉式

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    
    //加锁,解决常规的懒汉式单例模式,存在线程安全的问题
    class Singleton3 {
    public:
    	static Singleton3* getinstance() {
    		//两个NULL可以提高获取实例效率
    		if (instance == NULL) {
    			mtx.lock();
    			if (instance == NULL) {
    				instance = new Singleton3();
    			}
    			mtx.unlock();
    		}
    		return instance;
    	}
    
    	void test() {
    		cout << "this is Singleton3..." << endl;
    	}
    
    private:
    	Singleton3() { cout << "Singleton3构造函数" << endl; }
    	Singleton3(const Singleton3&) {}
    	Singleton3& operator=(const Singleton3&) {}
    	static Singleton3* instance;
    
    	static mutex mtx;	//锁
    
    	//类中嵌套类,用于自动回收资源
    	class GC {
    	public:
    		~GC() {
    			if (instance != NULL) {
    				delete instance;
    				instance = NULL;
    				cout << "GC3自动回收..." << endl;
    			}
    			cout << "GC3..." << endl;
    		}
    	};
    	static GC gc;
    };
    Singleton3* Singleton3::instance = NULL;
    mutex Singleton3::mtx;
    Singleton3::GC Singleton3::gc;
    
    int main() {
    	Singleton3::getinstance()->test();
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    结果:
    在这里插入图片描述

    饿汉式

    饿汉式:饿了肯定要饥不择食。在单例类定义的时候就进行实例化。
    饿汉式没有线程安全问题。
    代码:

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    
    //饿汉式单例模式,不需要加锁,也不存在线程安全问题
    class Singleton4 {
    public:
    	static Singleton4* getInstance() {
    		return instance;
    	}
    
    	void test() {
    		cout << "this is Singleton4..." << endl;
    	}
    
    private:
    	Singleton4() { cout << "Singleton4构造函数" << endl; }
    	Singleton4(const Singleton4&){}
    	Singleton4& operator=(const Singleton4&) {}
    	static Singleton4* instance;
    
    	//类中嵌套类,用于自动回收
    	class GC {
    	public:
    		~GC() {
    			if (instance != NULL) {
    				delete instance;
    				instance = NULL;
    				cout << "GC4自动回收..." << endl;
    			}
    			cout << "GC4..." << endl;
    		}
    	};
    	static GC gc;
    };
    Singleton4* Singleton4::instance = new Singleton4();
    Singleton4::GC Singleton4::gc;
    
    int main() {
    	Singleton4::getInstance()->test();
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    结果:
    在这里插入图片描述

    c++单例模式为什么不在析构函数中释放静态的单例对象

    1、单例中的 new 的对象需要delete释放。

    2、delete释放对象的时候才会调用对象的析构函数。

    3、如果在析构函数里调用delete,那么程序结束时,根本进不去析构函数,怎么会delete。

    4、如果程序结束能自动析构,那么就会造成一个析构的循坏,所以new对应于delete。

    经过验证,如果不手动delete对象,符合第3条,因为只有进行了delete的时候才会执行析构函数,但是delete是在析构函数中执行的,不在外部手动delete,则不会执行析构函数,所以在析构函数中释放对象也没有用。

    但是,如果手动delete对象,将会出现死循环,也就是出现了第4条的情况。因为手动delete会调用析构函数,而在析构函数中,又使用了delete静态的单例对象,进而又会调用析构函数,这样就会一直循环下去,直到栈溢出。

    综上所述,不可以在析构函数中释放静态的单例对象。可以在类中嵌套一个回收的类,用于释放单例的对象。

    参考链接:
    C++ 单例模式 - 泣血 - 博客园 (cnblogs.com)

    C++实现单例模式_Elena-N的博客-CSDN博客_单例模式c++实现

    【设计模式】单例模式,嵌套类实现释放对象内存 - 走看看 (zoukankan.com)

    C++单例模式为何要实例化一个对象不全部使用static_C 语言_脚本之家 (jb51.net)

    C++单例模式 正确的资源回收方式_ice_ly000的博客-CSDN博客

  • 相关阅读:
    Android AlertDialog样式调整
    基于ArcGIS道路密度指数的计算及可视化制图(附练习数据下载)
    链表中面试常考题
    InfluxDB 内存消耗分析及性能优化
    机器学习——逻辑斯蒂回归数学原理及信用卡诈骗项目实例
    PyTorch 单机多GPU 训练方法与原理整理
    浅谈智能安全配电装置应用在银行配电系统中
    边玩边学!交互式可视化图解!快收藏这18个机器学习和数据科学网站!
    使用spring-boot-starter-jdbc访问MySQL大部分程序员都不一定会
    anaconda+python+pycharm代码学习——自动化办公(一)——excel自动化处理
  • 原文地址:https://blog.csdn.net/weixin_45003868/article/details/125608685