最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何在 C++ 中實(shí)現(xiàn)一個(gè)單例類模板

 更新時(shí)間:2020年10月28日 10:00:08   作者:始終  
這篇文章主要介紹了如何在 C++ 中實(shí)現(xiàn)一個(gè)單例類模板,幫助大家更好的理解和學(xué)習(xí)c++編程,感興趣的朋友可以了解下

單例模式是最簡單的設(shè)計(jì)模式之一。在實(shí)際工程中,如果一個(gè)類的對象重復(fù)持有資源的成本很高,且對外接口是線程安全的,我們往往傾向于將其以單例模式管理。

此篇我們在 C++ 中實(shí)現(xiàn)正確的單例模式。

選型

在 C++ 中,單例模式有兩種方案可選。

  • 一是實(shí)現(xiàn)一個(gè)沒有可用的公開構(gòu)造函數(shù)的基類,并提供 GetInstance 之類的靜態(tài)接口,以便訪問子類唯一的對象。由于子類構(gòu)造必須調(diào)用基類構(gòu)造,但基類無公開構(gòu)造函數(shù)可用,這使得子類對象只能由基類及基類的友元來構(gòu)造,從而在機(jī)制上保證單例。
  • 二是實(shí)現(xiàn)一個(gè)類模板,其模板參數(shù)是希望由單例管理的類的名字,并提供 GetInstance 之類的靜態(tài)接口。這種做法的好處是希望被單例管理的類,可以自由編寫,而無需繼承基類;并且在需要的時(shí)候,可以隨時(shí)脫去單例外衣。

此篇選擇實(shí)現(xiàn)一個(gè)單例類模板,其形如:

template <typename T>
struct Singleton {
 static T* get();
 T* operator->() const {
 return get();
 }
};

這里重載成員訪問運(yùn)算符,是為了可以實(shí)現(xiàn)這樣的簡寫 Singleton<T>()->func()。

顯然,單例的實(shí)現(xiàn)核心在于靜態(tài)成員函數(shù) T* get()。

一個(gè)典型的錯誤實(shí)現(xiàn)

一個(gè)典型的錯誤實(shí)現(xiàn),是使用所謂的雙重檢查(double check)。

#include <mutex>

template <typename T>
struct Singleton {
 static T* get() {
 static T* p{nullptr};
 if (nullptr == p) {
  std::lock_guard<std::mutex> lock{mtx};
  if (nullptr == p) {
  p = new T;
  }
 }
 return p;
 }
 T* operator->() const {
 return get();
 }

 private:
 static std::mutex mtx;
};

template <typename T>
std::mutex Singleton<T>::mtx;

外層的檢查,是為了避免鎖住過大的區(qū)域,從而導(dǎo)致鎖的競爭特別頻繁;內(nèi)層的檢查,是為了確保只在別的線程沒有提前搶占鎖完成初始化工作而設(shè)計(jì)的。這種做法在 Java 下是正確的,但是在 C++ 下則沒有保證。

另外,值得一提的是,這里 p 的初始化的線程安全性,是由 C++ 標(biāo)準(zhǔn)保證的。——在 C++11 之后,標(biāo)準(zhǔn)保證函數(shù)靜態(tài)成員的初始化是線程安全的;對其讀寫則不保證線程安全。

使用標(biāo)準(zhǔn)庫提供的設(shè)施

在單例的實(shí)現(xiàn)中,我們實(shí)際上是希望實(shí)現(xiàn)「執(zhí)行且只執(zhí)行一次」的語義。C++11 之后,標(biāo)準(zhǔn)庫實(shí)際已經(jīng)提供了這樣的設(shè)施。其名為 std::once_flag std::call_once。它們內(nèi)部利用互斥量和條件變量組合,實(shí)現(xiàn)這樣的語義。值得一提的是,如果執(zhí)行過程中拋出異常,標(biāo)準(zhǔn)庫的設(shè)施不認(rèn)為這是一次「成功的執(zhí)行」。于是其他線程可以繼續(xù)搶占鎖來執(zhí)行函數(shù)。

我們利用標(biāo)準(zhǔn)庫設(shè)施來實(shí)現(xiàn)這個(gè)類模板。

#include <mutex>

template <typename T>
struct Singleton {
 static T* get() {
 static T* p{nullptr};
 std::call_once(flag, [&]() -> void {
  p = new T;
 });
 return p;
 }
 T* operator->() const {
 return get();
 }

 private:
 static std::once_flag flag;
};

template <typename T>
std::once_flag Singleton<T>::flag;

于是你可以寫出類似這樣的代碼:

#include <mutex>
#include <iostream>
#include <future>
#include <vector>

#include "singleton.h"

struct Foo {
 void address() const {
 std::lock_guard<std::mutex> lock{mtx};
 std::cout << static_cast<void*>(const_cast<Foo*>(this)) << '\n';
 }
 mutable std::mutex mtx;
};

int main() {
 Singleton<Foo>()->address();
 std::vector<std::future<void>> futs;
 for (size_t i = 0; i != 10; ++i) {
 futs.emplace_back(std::async(&Foo::address, Singleton<Foo>::get()));
 }
 for (auto& fut : futs) {
 fut.get();
 }
 return 0;
}

得到的輸出類似這樣:

$ ./a.out
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10

Bonus:需要注意的是,所有的 std::once_flag 內(nèi)部共享了同一對互斥量和條件變量。因此當(dāng)存在很多 std::call_once 的時(shí)候,性能會有所下降。這一點(diǎn)可能需要注意一下。不過,如果存在很多 std::call_once,大概也說明程序設(shè)計(jì)不合理吧……

Bonus:注意我們這里沒有釋放 p 指向的對象。這是因?yàn)?C++ 程序?qū)o態(tài)變量的析構(gòu)順序是不確定的。如果靜態(tài)變量之間有相互依賴,析構(gòu)被依賴的對象可能會導(dǎo)致段錯誤。因此干脆就不釋放了,這是所謂的 LeakySingleton。當(dāng)然,如果你的工程當(dāng)中有實(shí)現(xiàn)一個(gè)通用的 ExitManager,是有可能正確析構(gòu)的。但考慮到還可能大量使用第三方庫,而第三方庫不可能使用你實(shí)現(xiàn)的 ExitManager,于是管理所有靜態(tài)變量的析構(gòu)又變得不可能,于是干脆就不管它了。

如此如此,這般這般

如果你仔細(xì)讀了這篇文章,你可能會忽然意識到剛才看到了這句話:「在 C++11 之后,標(biāo)準(zhǔn)保證函數(shù)靜態(tài)成員的初始化是線程安全的;對其讀寫則不保證線程安全?!?/p>

既然如此,我們?yōu)樯哆€要費(fèi)勁使用 std::once_flagstd::call_once 呢?直接利用 static hack 出一個(gè)單例類模板不就好了嗎?

template <typename T>
struct Singleton {
 static T* get() {
 static T ins;
 return &ins;
 }
 T* operator->() const {
 return get();
 }
};

以上就是如何在 C++ 中實(shí)現(xiàn)一個(gè)單例類模板的詳細(xì)內(nèi)容,更多關(guān)于c++ 單例類模板的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Qt實(shí)現(xiàn)小功能之圓形進(jìn)度條的方法詳解

    Qt實(shí)現(xiàn)小功能之圓形進(jìn)度條的方法詳解

    在Qt自帶的控件中,只有垂直進(jìn)度條、水平進(jìn)度條兩種。在平時(shí)做頁面開發(fā)時(shí),有些時(shí)候會用到圓形進(jìn)度條,比如說:下載某個(gè)文件的下載進(jìn)度。本文就來實(shí)現(xiàn)一個(gè)圓形進(jìn)度條,需要的可以參考一下
    2022-10-10
  • C++?OpenCV裁剪圖片時(shí)發(fā)生報(bào)錯的解決方式

    C++?OpenCV裁剪圖片時(shí)發(fā)生報(bào)錯的解決方式

    在圖像處理中,我們經(jīng)常根據(jù)需要截取圖像中某一區(qū)域做處理,下面這篇文章主要給大家介紹了關(guān)于C++?OpenCV裁剪圖片時(shí)發(fā)生報(bào)錯的解決方式,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • C語言編程實(shí)例之輸出指定圖形問題

    C語言編程實(shí)例之輸出指定圖形問題

    這篇文章主要介紹了C語言編程實(shí)例之輸出指定圖形問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • C語言中的三種循環(huán)語句

    C語言中的三種循環(huán)語句

    這篇文章主要介紹了C語言中的三種循環(huán)語句,在C語言中循環(huán)可分為3中,while循環(huán)、do - while循環(huán)、for 循環(huán)。下面小編就來為大家講解這三種循環(huán)語句,需要的朋友可以參考一下,希望對你有所幫助
    2021-10-10
  • C++中NULL與nullptr的區(qū)別對比

    C++中NULL與nullptr的區(qū)別對比

    nullptr是c++11中的關(guān)鍵字,下面這篇文章主要介紹了C++中NULL與nullptr區(qū)別的相關(guān)資料,對大家來說還是挺實(shí)用的,需要的朋友可以參考下
    2021-05-05
  • 基于內(nèi)核線程的創(chuàng)建、使用和退出以及延時(shí)宏的補(bǔ)充說明介紹

    基于內(nèi)核線程的創(chuàng)建、使用和退出以及延時(shí)宏的補(bǔ)充說明介紹

    本篇文章是對內(nèi)核線程的創(chuàng)建、使用和退出以及延時(shí)宏的補(bǔ)充說明介紹進(jìn)行了敘述。需要的朋友參考下
    2013-05-05
  • C語言深入分析遞歸函數(shù)的實(shí)現(xiàn)

    C語言深入分析遞歸函數(shù)的實(shí)現(xiàn)

    遞歸(recursive)函數(shù)是“自己調(diào)用自己”的函數(shù),無論是采用直接或間接調(diào)用方式。間接遞歸意味著函數(shù)調(diào)用另一個(gè)函數(shù)(然后可能又調(diào)用第三個(gè)函數(shù)等),最后又調(diào)用第一個(gè)函數(shù)。因?yàn)楹瘮?shù)不可以一直不停地調(diào)用自己,所以遞歸函數(shù)一定具備結(jié)束條件
    2022-04-04
  • C++實(shí)現(xiàn)數(shù)獨(dú)快速求解

    C++實(shí)現(xiàn)數(shù)獨(dú)快速求解

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)數(shù)獨(dú)快速求解的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C語言中g(shù)etchar()與putchar()函數(shù)詳解

    C語言中g(shù)etchar()與putchar()函數(shù)詳解

    本文主要介紹了C語言中g(shù)etchar()與putchar()函數(shù)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • C++常用字符串函數(shù)大全(2)

    C++常用字符串函數(shù)大全(2)

    這篇文章主要給大家分享的是C++常用字符串函數(shù)的大全,cstring.h庫即C語言中的string.h庫,它是C語言中為字符串提供的標(biāo)準(zhǔn)庫。C++對此進(jìn)行了兼容,所以我們在C++當(dāng)中一樣可以使用,下面文章的詳細(xì)內(nèi)容,需要的朋友可以參考一下
    2021-11-11

最新評論

林芝县| 谢通门县| 江油市| 定日县| 巫山县| 巨野县| 新宾| 宁河县| 铅山县| 肇庆市| 平阴县| 突泉县| 怀来县| 若尔盖县| 靖西县| 安庆市| 宁波市| 丁青县| 漯河市| 手游| 阳山县| 浦江县| 洛隆县| 阳高县| 泉州市| 伊通| 措美县| 松桃| 陵水| 日照市| 车致| 福鼎市| 若尔盖县| 澄江县| 呼图壁县| 承德县| 拜城县| 星子县| 石门县| 清流县| 婺源县|