说明
单例模式,面试中经常被问到,但是很多人只会最简单的单例模型,可能连多线程都没考虑到,本文章从最简单的单例,到认为是最佳的单例模式实现方式,单例模式没有什么知识点,直接上源码
源代码
版本一
这个版本是最简单的版本,但是存在2个问题:(1)不支持多线程(2)需要主动调用函数来释放对象,否则程序结束后,不会调用析构函数,这可能会造成严重的后果,比如单例类对象需要在释放时做一些网络数据传输的工作,最后释放连接,在关闭连接前,会做一些判断,是否可以释放连接,可以释放连接时,才去释放连接,这些都可以在析构函数里完成
//缺点: //1.需要主动去释放单例对象,否则会造成内存泄漏 //2.不支持多线程 class Singleton { public: static Singleton* GetInstance() { if (_instance == nullptr) { _instance = new Singleton(); } return _instance; } // static void freeInstance() { if (_instance != nullptr) { delete _instance; } _instance = nullptr; } private: Singleton() { std::cout << "new Singleton" << std::endl; } ~Singleton() { std::cout << "~Singleton" << std::endl; }// Singleton(const Singleton& clone) = delete; Singleton& operator=(const Singleton&) = delete; //存放在静态区,程序结束,生命周期结束,只是将指针设置为nullptr,并没有去调用析构函数,如果想被释放, //必须加一个函数去主动释放,那么通常就需要将释放函数放到程序结束的位置(否则单例就没有意义),如果一个系统中需要使用多个单例 //会调用释放函数,不方便 static Singleton* _instance; }; Singleton* Singleton::_instance = nullptr;//静态变量必须初始化
版本2
版本1需要主动去释放对象不能主动释放对象的空间,有了版本2
//版本1需要主动释放单例,不太方便,因此这里解决主动释放的问题,使用atexit:在程序结束后自动去调用析构函数,释放资源 class Singleton2 { public : static Singleton2* GetInstance() { if (_singleton == nullptr) { _singleton = new Singleton2(); atexit(destructor);//释放操作,不会造成内存泄漏或者资源没有被释放的问题 } return _singleton; } private: static void destructor() { if (nullptr != _singleton) { delete _singleton; } _singleton = nullptr; } Singleton2() { std::cout << "new Singleton2" << std::endl; } ~Singleton2() { std::cout << "~Singleton2" << std::endl; } Singleton2(const Singleton2& cpy) = delete; Singleton2& operator=(const Singleton2& other) = delete; static Singleton2* _singleton; }; Singleton2* Singleton2::_singleton = nullptr;//静态变量必须初始化
版本3
解决版本1的2个问题,但是这个版本有一个隐含的知识点,很多人不知道这个知识点,就很难发现这个版本的问题所在
//这个例子好像没什么问题,一般人也很难发现,但是这个如果上线,会出现莫名其妙的错误,主要原因在new实例的时候说明 class Singleton3 { public: static Singleton3* GetInstance() { //std::lock_guard<std::mutex> lock(mutex);//枷锁不要加在这里,这里锁的范围太大了,单例模式,主要是读取 //如果在这里枷锁,基本每次调GetInstance都会枷锁,性能不高。 if (instance == nullptr) { std::lock_guard<std::mutex> lock(mutex); //为什么需要双重判断,有可能个进程同时进入到外层if(instance == nullptr),这时候一个线程进入到这里 //另一个线程会卡住,当第一个线程释放时,如果不进行null判断,会造成构造2次 if (instance == nullptr) { //下一句主要分为3个步骤 //1.分配空间malloc //2.构造函数初始化 //3.赋值给instance //通常情况下,cpu为了效率,可能会出现1,2,3,也可能会出现1,3,2 //当执行顺序为1,3,2时,当这个线程执行完1,3步骤时,假如另一个线程恰好执行到外层if,那么由于已经赋值 //但是赋值却不是初始化的值,其他线程去执行的时候拿到的就是错误的数据,这就是多线程会导致的问题。 //可以使用内存屏障解决,这里就不讲了,请自行参考其他文章 instance = new Singleton3();// atexit(deconstractor); } } return instance; } private: static void deconstractor() { if (instance != nullptr) { delete instance; } instance = nullptr; } Singleton3() { std::cout << "new Singleton3" << std::endl; } ~Singleton3() { std::cout << "~Singleton3" << std::endl; } Singleton3(const Singleton3& cpy) = delete; Singleton3& operator=(const Singleton3& other) = delete; static Singleton3* instance; static std::mutex mutex; }; Singleton3* Singleton3::instance = nullptr; std::mutex Singleton3::mutex;
版本4
解决CPU reorder的问题,比较好的解决方案
//局部静态对象,生命周期结束后会自动释放空间, //而且static也是线程安全的,假如2个线程同时调用GetInstance,不会创建2个instance class Singleton5 { public: static Singleton5& GetInstance() { static Singleton5 instance; return instance; } private: Singleton5() { std::cout << "new Singleton5" << std::endl; } ~Singleton5() { std::cout << "~Singleton5" << std::endl; } Singleton5(const Singleton5&) = delete; Singleton5& operator=(const Singleton5& other) = delete; };
版本6
版本5 不利于扩展,使用模板来解决所有类的单例模式
//上面一个版本已经很好了,但是扩展性差,只能用于一个类的单例,使用模板来实现任何类的单例 template <typename T> class Singleton6{ public: static T& GetInstance() { //T是子类,返回子类对象的引用 static T instance; return instance; } protected://子类可访问父类的构造函数,初始化和释放对象时,都会调用父类的构造函数 Singleton6() { std::cout << "new Model Singleton6" << std::endl; } ~Singleton6() { std::cout << "~Model Singleton6" << std::endl; } Singleton6(const Singleton6&) = delete; Singleton6& operator=(const Singleton6& other) = delete; }; //注意,父类为模板类Singleton6<DesignPattern>,子类为DesignPattern,将子类传递到父类 class DesignPattern :public Singleton6<DesignPattern> { //由于对象是在父类对象构造的,而我们将构造和析构设置为private,父类要想访问,就需要将其设置为友元类 friend Singleton6<DesignPattern>; private: DesignPattern() { std::cout << "new DesignedPattern" << std::endl; } ~DesignPattern() { std::cout << "~DesignPattern" << std::endl; } DesignPattern(const DesignPattern& cpy) = delete; DesignPattern& operator=(const DesignPattern& other) = delete; };