什么是單例模式?
一種創建型的設計模式,該模式的主要目的就是確保某個類有且僅有一個實例存在。
單例模式有三個關鍵點:
1、單例類只能有一個實例。
為此,單例類只能提供私有的構造函數,即保證不能隨意創建該類的實例。
2、單例類必須自己創建自己的唯一實例。
因為構造函數是私有的,其他對象不能創建單例類的實例,只能是單例類自己來創建。
3、單例類必須給所有其他對象提供這一實例。
外界需要獲取並使用這個單例類的實例,但是由於該類的構造函數是私有的,外界無法通過new去獲取它的實例,那么就必須提供一個靜態的公有方法,該方法創建或者獲取它本身的靜態私有對象並返回。
單例模式有兩種實現方式:
懶漢式:故名思義,懶漢很懶只有餓了才會去找吃的。也就是說,只有在需要使用的時候才會去實例化。
餓漢式:餓了肯定要飢不擇食。在單例類定義的時候就進行實例化。
懶漢式單例模式
//Singleton.h #pragma once class Singleton { public: static Singleton* getInstance(); private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); static Singleton* instance_; }; //Singleton.cpp #include <iostream> #include "Singleton.h" Singleton* Singleton::instance_ = NULL; Singleton::Singleton() { } Singleton::Singleton(const Singleton&) { } Singleton & Singleton::operator=(const Singleton&) { } Singleton * Singleton::getInstance() { if (NULL == instance_) { instance_ = new Singleton(); } return instance_; }
(1)默認構造函數是私有的,外部不能進行單例類的實例化;
(2)拷貝構造函數和賦值運算符也是私有的,以禁止拷貝和賦值;
(3)具有一個私有的靜態成員指針 instance_,指向唯一的實例;
(4)提供一個公有的靜態成員函數用於返回實例,如果實例為NULL,則進行實例化。
餓漢式單例模式
//Singleton.h #pragma once class Singleton { public: static Singleton* getInstance(); private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); static Singleton* instance_; }; //Singleton.cpp #include <iostream> #include "Singleton.h" Singleton* Singleton::instance_ = new Singleton(); Singleton::Singleton() { } Singleton::Singleton(const Singleton&) { } Singleton & Singleton::operator=(const Singleton&) { } Singleton * Singleton::getInstance() { return instance_; }
與懶漢式單例模式不同之處是,在全局作用域進行單例類的實例化,並用此實例初始化單例類的靜態成員指針instance_。
線程安全問題
懶漢式:如果有兩個線程同時獲取單例類的實例,都發現實例不存在,因此都會進行實例化,就會產生兩個實例都要賦值給instance_,這是嚴重的錯誤。為了解決這個問題,就要考慮加鎖。
線程安全的懶漢式,修改獲取實例的方法如下:
Singleton * Singleton::getInstance() { lock(); //上鎖 if (NULL == instance_) { instance_ = new Singleton(); } unlock(); return instance_; }
但這個獲取實例的方法存在性能問題,每次獲取實例的時候都要先上鎖,之后再解鎖,如果有很多線程的話,可能會造成大量線程的阻塞。改進后的實現如下:
Singleton * Singleton::getInstance() { if (NULL == instance_) { lock(); //上鎖 if (NULL == instance_) { instance_ = new Singleton(); } unlock(); } return instance_; }
絕大多數情況下,獲取實例時都是直接返回實例,這時候不會涉及到上鎖、解鎖的問題。只有在沒有實例的時候,才會涉及上鎖、解鎖,這種情況是很少的。這個獲取實例的方法對性能幾乎無影響。
餓漢式:程序運行初期就進行了單例類實例化,不存在上述的線程安全問題。
對象釋放問題
上邊的程序中只有new,卻沒有delete,也就是說只有內存申請而沒有內存釋放,會不會有內存泄漏?
答:一般情況下,單例類的實例都是常駐內存的,一直存在於進程的生命周期,因此不需要手動釋放。如果的確需要釋放實例占用的內存,一定不能在單例類的析構函數中進行delete操作,這樣會造成無限循環,可以考慮增加一個destroy方法用於釋放內存,或者在單例類中定義一個內嵌的垃圾回收類,詳情請參考最后兩個參考鏈接。
參考與鳴謝:
https://www.cnblogs.com/cxjchen/p/3148582.html
https://www.cnblogs.com/qiaoconglovelife/p/5851163.html
https://blog.csdn.net/stpeace/article/details/68953096
https://blog.csdn.net/zhanghuaichao/article/details/79459130
https://blog.csdn.net/chenyingying_/article/details/83029600
https://www.cnblogs.com/xzy1210/p/3849253.html