單例概述 單例意即類在整個工程里只能有一個實例。單例通常應用在如下場景中,類的構造是一個非常耗時的過程,並且,它沒有多次構造的必要性。例如。你可以打開一個資料庫連接,只在此連接上進行資料庫操作。 那麼怎確保它在整個工程中只有一個實例呢?我們可以通過將構造函數的訪問許可權設置為private,並輔助其它 ...
單例概述
單例意即類在整個工程里只能有一個實例。單例通常應用在如下場景中,類的構造是一個非常耗時的過程,並且,它沒有多次構造的必要性。例如。你可以打開一個資料庫連接,只在此連接上進行資料庫操作。
那麼怎確保它在整個工程中只有一個實例呢?我們可以通過將構造函數的訪問許可權設置為private,並輔助其它手段來保證,同時將拷貝構造函數和賦值構造函數聲明為delete的。
知識儲備
作用域
在C++中,變數根據定義的位置不同具有不同的生命周期,具體分為六種:語句作用域、類作用域、全局作用域、文件作用域、命名空間作用域、局部作用域(函數或者語句塊)。相應的變數也分為局部變數、全局變數、局部靜態變數和全局靜態變數。
局部變數、全局變數、局部靜態變數、全局靜態變數
生存周期和作用域:生存周期指的是變數從定義開始到銷毀經歷的時間範圍,而作用域指的是變數的可見代碼域。
局部變數:局部變數具有局部作用域,例如函數的形參,定義在函數中的變數。從存儲空間上來看,局部變數是在棧上分配空間的。從生存周期來看,它僅存在與被定義時到離開局部作用域的那一刻。
全局變數:全局變數具有全局作用域,意即,一個全局變數只能有一個定義,可以有多個聲明,其它文件需要使用這個全局變數的話,需要使用extern進行聲明,它被定義於任何函數(包括main函數)之外。從存儲的角度來看,它被保存在了ELF的.data段或者.bss段(根據是否被初始化而定)。從生存周期來看,它存在於整個程式運行期間,直到程式退出。
靜態局部變數:靜態局部變數就是在局部變數的前面加了static修飾符,它的作用域範圍和局部變數相同,生存周期從定義時起,到進程結束時由操作系統負責銷毀。從空間分配上來說它在ELF的.data段。
靜態全局變數:靜態全局變數就是在全局變數的前面加了static修飾符,它具有文件作用域,所謂文件作用域即指這個變數僅在定義它的文件中生效,對其它文件不可見,就是說可以在文件A和文件B中定義兩個同名的靜態全局變數。從空間分配的角度看,它在ELF文件的data段。
那麼,類的數據成員怎麼分類呢?實際上,類的數據成員不適用於上述分類方式。普通數據成員就是類的實例的一部分,實例在,在數據成員在,實例不在,則數據成員亡。對靜態數據成員,它則是屬於類本身的,假設我們有一個數據,需要多個對象共用,那麼可以使用靜態數據成員。
單例-餓漢模式
所謂餓漢模式即指無論該單例在工程中是否使用,都創建好這個單例。在C++11下餓漢模式的構建利用了靜態變數在main函數開始執行前即初始化的行為。具體實現如下:
#ifndef SINGLETON_H_ #define SINGLETON_H_ #include <string> #include <iostream> class Singleton { private: static Singleton *m_SingleInstance; std::string m_strInfo; Singleton(const Singleton &) = delete; //copy Construct Singleton& operator=(const Singleton &)=delete; //assign Construct Singleton() { std::cout << "I am Constructed!"<< std::endl; } ~Singleton() = default; public: void setInfo(const std::string strInfo) { m_strInfo = strInfo; } void getInfo(std::string &strInfo) { strInfo = m_strInfo; } static Singleton* getInstance(); }; #define SINGLETON Singleton::getInstance() #endif
#include "Singleton.h" Singleton* Singleton::m_SingleInstance = new Singleton(); Singleton* Singleton::getInstance() { return m_SingleInstance; }
#include "Singleton.h" using namespace std; int main(int argc,char *argv[]) { return(1); }
還有一個小小的MakeFile,第一次寫這個歡迎指正:
VPATH = ../ main:Singleton.o main.o g++ -g $^ -o main.out -std=c++11 main.o:main.cpp g++ -g -c $^ -o $@ -std=c++11 Singleton.o:Singleton.cpp g++ -g -c $^ -o $@ -std=c++11 .PHONY:clean clean: rm -r *.*
可以看到這個main函數里沒有任何對單例的引用,使用GDB調試,在main函數入口處打上斷點,可以看到在main函數還沒有進入時就列印了Singleton的構造函數內的輸出信息,這意味著類的靜態數據成員是在main函數進入之前就被初始化了的,所以使用這種方式創建的單例沒有線程安全的隱患。