C 單例模式

2021-09-12 18:39:26 字數 4521 閱讀 5828

在一些情形下,保持類的例項只有乙個非常重要。例如:乙個表示檔案系統的class。乙個作業系統一定是只有乙個檔案系統的,因此,我們希望表示檔案系統的類例項有且僅有乙個。單例模式 是設計模式中一種實現這一類需求的設計方法。

單例模式(singleton),保證乙個類僅有乙個例項,並提供乙個訪問它的全域性訪問點。 [1]

全域性靜態變數能夠實現物件的全域性訪問,但這不能防止你例項化多個類例項。為了實現上述要求,我們需要加強類的設計,讓類自身保證其實例僅有乙個。也就是說,「這個類可以保證沒有其它例項可以被建立,並且它可以提供乙個訪問該例項的方法。」[1]

一般來說,單例類的結構如下:

class singleton ;
c++規定,non-local static 物件的初始化發生在main函式執行之前。但c++沒有規定多個non-local static 物件的初始化順序,尤其是來自多個編譯單元的non-local static物件,他們的初始化順序是隨機的。 

然而,對於local static 物件,其初始化發生在控制流第一次執行到該物件的初始化語句時。 non-local static物件的初始化發生在main函式之前的單執行緒啟動階段,所以無需擔心執行緒安全問題。但是local static物件則不同,多個執行緒的控制流可能同時到達其初始化語句。

在c++11之前,在多執行緒環境下local static物件的初始化並不是執行緒安全的。[2]具體表現就是:如果乙個執行緒正在執行local static物件的初始化語句但還沒有完成初始化,此時若其它執行緒也執行到該語句,那麼這個執行緒會認為自己是第一次執行該語句並進入該local static物件的建構函式中。這會造成這個local static物件的重複構造,進而產生記憶體洩露問題。

在文章的後半部分會看到,local static 物件在單例模式中有著廣泛的應用,為了解決local static物件在多執行緒環境下的重複構造問題,程式設計師想出了很多方法。而c++11則在語言的規範中解決了這個問題。c++11規定,在乙個執行緒開始local static 物件的初始化後完成初始化前,其他執行緒執行到這個local static物件的初始化語句就會等待,直到該local static 物件初始化完成。 [2]

單例模式的實現分為兩大類,懶漢模式和餓漢模式。懶漢模式的單例秉承著例項能晚一點構造就晚一點構造的思想,直到第一次使用單例時才構造單例;餓漢模式則恰好相反,即使例項永遠不會被使用,例項的構造還是會早早的發生。

懶漢模式有以下幾點要求:保證類例項是唯一的;提供全域性可訪問點;延遲構造,直到第一次使用該例項。 

以下是幾種懶漢模式的單例模式實現.[3][8] 他們或者利用了local static 物件的特性,或者使用指標來判斷物件是否是第一次初始化。在多執行緒條件下,則需要互斥鎖以避免重複構造問題 

首先是《設計模式》[1]中給出的單例模式實現。

templatet& singleton()
這一實現在c++98中不是執行緒安全的,具體原因參考上一節關於static物件的初始化的討論。

std::mutex m;  //必須是乙個全域性變數而不是乙個區域性變數

templatet& singleton()

但是,執行緒安全問題只是出現在第一次初始化過程。然而,以上**卻為了一次初始化而使得每一次獲取singleton都要首先獲取鎖資源,singleton的訪問變成了序列。這顯然不太合算。

為了避免每一次都加鎖我們可以事先判斷是否已經初始化。double check lock 是一種常用的實現手法。如下:

class singleton;

class singleton

}return *m_instance;

}private:

static singleton *m_instance = nullptr;

static std::mutex m;

//other class members

}

如果記憶體訪問嚴格按照語句先後順序進行,那麼以上**堪稱完美解決了所有問題。但是,在某些記憶體模型中(雖然不常見)或者是由於編譯器的優化以及執行時優化等等原因,使得m_instance雖然已經不是nullptr但是其所指物件還沒有完成構造,這種情況下,另乙個執行緒如果呼叫getinstance()就有可能使用到乙個不完全初始化的物件。(double check 中的互斥鎖在此處是不起作用的) 

記憶體屏障 [7]可以強制要求記憶體完成屏障前的所有記憶體讀寫操作。c++11提供了atomic實現記憶體的同步訪問,即不同執行緒總是獲取物件修改前或修改後的值,無法在物件修改期間獲得該物件。參考資料3中給出了多個實現記憶體同步訪問的示例。以下是利用atomic實現的版本:

class singleton;

class singleton

}return *m_instance;

}private:

static std::atomicm_instance(nullptr);

static std::mutex m;

//other class members

}

綜合以上種種我們發現,迫使我們做各種繁瑣工作的罪惡**是c++98中沒有規定local static 物件在多執行緒條件下的初始化行為。然而在上一節的討論中我們知道c++11給出了規定。[2]。因此,在c++11標準下乙個執行緒安全的單例模式可以這樣實現。這種方法也被稱為meyers』 singleton:

class singleton;

class singleton

private:

//class members

}

是不是有一種大道至簡的感覺?這和我們最開始給出的形式一模一樣。但是注意,此時的語言標準變了,在c++11標準下這樣寫才是執行緒安全的。另外gcc4.3和vs2015之後的版本也開始支援該特性。[2]。

餓漢模式下同樣要求單例是類的唯一例項,且需要類提供乙個全域性訪問點。但是與懶漢模式不同,餓漢模式中單例的構造會盡可能早的進行,即使目前不會用到,甚至以後永遠也不會用到。 

以下是幾種餓漢模式的單例模式實現.[4] 從上一節的討論中我們可以知道 non-local static物件符合餓漢模式對於單例構造應該盡早進行的要求。然而,一旦使用了non-local static物件我們就不得不小心多個non-local static 物件的初始化問題。[6]

class singleton 

~instancecreator()

};public:

static singleton& getinstance()

private:

friend class instancecreator;

static singleton *m_instance;

static instancecreator m_creator;

};

如果沒有其他non-local static 物件使用這個單例,那麼上述**是可以正常使用的。 

然而,在有其他non-local static 物件使用該單例時,上述**就會出現問題。由於non-local static物件初始順序的不確定性,我們無法讓具有依賴關係的兩個non-local static物件按照依賴關係的固定順序初始化。

事實上,在絕大多數條件下,類的設計階段我們無法得知會不會有乙個non-local static 物件依賴於這個單例,即使可以確定目前沒有其他non-local static物件依賴於這個單例,那也無法確定在以後也不會有。所以我們需要設計乙個方案來應對可能出現的兩種情況。以下**可以滿足這種需求

class singleton 

~instancecreator()

};struct instanceconfirm

}public:

static singleton& getinstance()

private:

friend class instancecreator;

friend class instanceconfirm;

static singleton *m_instance;

static instanceconfirm m_confirm;  //最後保證main函式之前完成初始化

};

以上**在c++98標準下也可以正常工作,唯一的要求是在main函式之前程序是單執行緒的。

在c++單例模式的多種實現中,meyers』 singleton是絕對的第一選擇!但要注意的是,其多執行緒安全性在c++11的標準下才能得以保證(或者編譯器特性支援)[2]。。

如果由於各種原因(包括為相容舊**等等)meyers』 singleton無法在多執行緒條件下工作。那麼,個人認為也沒有必要為了強行實現懶漢模式而搞一大堆的同步**[3][7][8] ,直接實現乙個餓漢模式的單例即可[4]。這一過程應該注意多個non-local static 物件的初始化次序問題[6]。

C 單例模式

include using namespace std 單例類的c 實現 class singleton 構造方法實現 singleton singleton void singleton setvar int var main int main int argc,char argv return ...

C 單例模式

實現方式一 include template typename t class singleton boost noncopyable static void init private static pthread once t ponce statict value template typena...

C 單例模式

效率有點低,但是還算安全的單例模式,靜態成員實現方式 class singleton public static singleton getinstance singleton singleton getinstance unlock return m instance 內部靜態例項的懶漢模式,c ...