日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++学习之路: 单例模板

發(fā)布時(shí)間:2025/3/19 c/c++ 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++学习之路: 单例模板 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

引言:

1.單例模式的目的:確保一個(gè)類只有一個(gè)實(shí) 例,并提供對(duì)該實(shí)例的全局訪問。

2.?單例模式也稱為單件模式、單子模式,可能是使用最廣泛的設(shè)計(jì)模式。其意圖是保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn),該實(shí)例被所有程序模塊共享。

單例模式 如何實(shí)現(xiàn)只有一個(gè)實(shí)例?? 禁用拷貝構(gòu)造函數(shù),防止拷貝。

那么還可以通過顯式定義來 定義多個(gè)實(shí)例。

如 ?NonCopyable a, b; 雖然禁止拷貝,但是可以定義多個(gè)對(duì)象。

所以我們把構(gòu)造函數(shù)也設(shè)為私有,這樣就不能通過顯式定義多個(gè)對(duì)象了。

?

那么如果把構(gòu)造函數(shù)設(shè)為私有, 該如何實(shí)例化這個(gè)類呢? 我們之前不是學(xué)過 如果把構(gòu)造函數(shù)設(shè)為私有, 這個(gè)便不可使用,那么不就沒有意義了嗎?

該如何解決這個(gè)問題呢? ?采用曲線救國的方式:在類內(nèi)部定義一個(gè)static 全局函數(shù), 讓它來替我們調(diào)用構(gòu)造函數(shù), 因?yàn)樵摵瘮?shù)在類的內(nèi)部, 所以可以訪問私有的 構(gòu)造函數(shù),又因?yàn)槭莝tatic函數(shù), 所以也可以再類外部直接調(diào)用。

?

既然static靜態(tài)函數(shù)在外部直接調(diào)用, 那么是否可以定義多個(gè)對(duì)象呢?

例如 1.getInstrance()

? ? ? ?2.getInstrance() ? ? 這樣調(diào)用兩次不就等于創(chuàng)建兩個(gè)對(duì)象了嗎??

  假如 類內(nèi)部定義一個(gè)Singleton*pInstance_, 一個(gè)指向自身類的 對(duì)象指針那么我們便可以通過

  static?getInstrance()來動(dòng)態(tài)創(chuàng)建一個(gè)自身類 的對(duì)象了。

  { static pInstance_ = new Singleton;?} 如此便實(shí)現(xiàn)了我們的要求了。

  但是也帶來了問題, 我們多次調(diào)用getInstance()便可以創(chuàng)建多個(gè)對(duì)象,

  如果pInstance_ 設(shè)置為非static, 在實(shí)例化為一個(gè)對(duì)象之前, 它是不存在的,想想 如果我們沒有定義一個(gè)string s,

不存在對(duì)象,就不存在pInstance_,那么如何調(diào)用里面的成員呢? ? 答案是 static 成員, 不管這個(gè)是否存在一個(gè)對(duì)象, 我們都可以訪問static成員。 static成員的存在 不依賴對(duì)象。 只有該類存在, 該指針就存在。

單例模式需要解決的問題:

?

a)????? 把構(gòu)造函數(shù)設(shè)為私有,禁用賦值和復(fù)制。帶來的問題:main中無法隨意生成對(duì)象

b)????? 提供一個(gè)static函數(shù)繞過構(gòu)造函數(shù)為private的限制。問題:對(duì)象不唯一。

c)????? 設(shè)置一個(gè)static指針,每次先判斷是否為NULL(防止重復(fù)new出新的對(duì)象導(dǎo)致內(nèi)存泄露)。此時(shí)實(shí)現(xiàn)了一個(gè)簡單的單例模式。但是此時(shí)在多線程環(huán)境下不唯一。

?

1. 如何構(gòu)造不可復(fù)制的類。

1 #include <iostream> 2 #include "Thread.h" 3 #include <stdlib.h> 4 using namespace std; 5 6 //多線程下具有隱患 7 class Singleton 8 { 9 public: 10 static Singleton *getInstance() 11 { 12 if(pInstance_ == NULL) //線程的切換 13 { 14 ::sleep(1); 15 pInstance_ = new Singleton; 16 } 17 18 return pInstance_; 19 } 20 private: 21 Singleton() { } 22 23 static Singleton *pInstance_; //靜態(tài)成員可以再外部直接調(diào)用 24 }; 25 26 Singleton *Singleton::pInstance_ = NULL; //這一行并不是調(diào)用,而是定義, 如果在main中這樣寫顯然是不行的,因?yàn)閜Instance_是一個(gè)私有成員 27 28 29 class TestThread : public Thread 30 { 31 public: 32 void run() 33 { 34 cout << Singleton::getInstance() << endl; 35 cout << Singleton::getInstance() << endl; 36 } 37 }; 38 39 int main(int argc, char const *argv[]) 40 { 41 //Singleton s; ERROR 42 43 44 //測(cè)試證明了多線程下本代碼存在競爭問題 45 46 TestThread threads[12]; 47 for(int ix = 0; ix != 12; ++ix) 48 { 49 threads[ix].start(); 50 } 51 52 for(int ix = 0; ix != 12; ++ix) 53 { 54 threads[ix].join(); 55 } 56 return 0; 57 }

?

1 0xb1300468 2 0xb1300498 3 0x9f88728 4 0xb1300498 5 0xb1300478 6 0xb1300498 7 0xb1100488 8 0xb1300498 9 0xb1300488 10 0xb1300498 11 0xb1300498 12 0xb1300498 13 0x9f88738 14 0xb1300498 15 0x9f88748 16 0xb1300498 17 0xb1100478 18 0xb1300498 19 0xb1100498 20 0xb1300498 21 0xb1100468 22 0xb1300498 23 0xb11004a8 24 0xb11004a8

多線程條件下打印出的結(jié)果。 多個(gè)對(duì)象其地址不同, 說明多線程new出了多個(gè) 對(duì)象,導(dǎo)致內(nèi)存泄露。

?

?

上述代碼在多線程下回出現(xiàn)明顯的 bug, ? 如果兩個(gè)線程同時(shí)運(yùn)行g(shù)etInstance_ ,且同時(shí)進(jìn)入判斷 pInstance_ 為空, 導(dǎo)致new 出多個(gè)Singleton對(duì)象, 但是pInstance_ 只能指向其中一個(gè), 那么就導(dǎo)致了內(nèi)存的泄露。

?

2. 為了解決線程同時(shí)訪問pInstance_的問題, 我們給它加上互斥鎖mutex;

?

1 #include <iostream> 2 #include "MutexLock.h" 3 #include "Thread.h" 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <string.h> 7 #include <sys/time.h> 8 using namespace std; 9 10 int64_t getUTime(); 11 12 //多線程下具有隱患 13 class Singleton 14 { 15 public: 16 static Singleton *getInstance() 17 { 18 mutex_.lock(); 19 if(pInstance_ == NULL) //線程的切換 20 pInstance_ = new Singleton; 21 mutex_.unlock(); 22 return pInstance_; 23 } 24 private: 25 Singleton() { } 26 27 static Singleton *pInstance_; 28 static MutexLock mutex_; 29 }; 30 31 Singleton *Singleton::pInstance_ = NULL; 32 MutexLock Singleton::mutex_; 33 34 class TestThread : public Thread 35 { 36 public: 37 void run() 38 { 39 const int kCount = 1000 * 1000; 40 for(int ix = 0; ix != kCount; ++ix) 41 { 42 Singleton::getInstance(); 43 } 44 } 45 }; 46 47 int main(int argc, char const *argv[]) 48 { 49 //Singleton s; ERROR 50 51 int64_t startTime = getUTime(); 52 53 const int KSize = 100; 54 TestThread threads[KSize]; 55 for(int ix = 0; ix != KSize; ++ix) 56 { 57 threads[ix].start(); 58 } 59 60 for(int ix = 0; ix != KSize; ++ix) 61 { 62 threads[ix].join(); 63 } 64 65 int64_t endTime = getUTime(); 66 67 int64_t diffTime = endTime - startTime; 68 cout << "cost : " << diffTime / 1000 << " ms" << endl; 69 70 return 0; 71 } 72 73 74 75 int64_t getUTime() 76 { 77 struct timeval tv; 78 ::memset(&tv, 0, sizeof tv); 79 if(gettimeofday(&tv, NULL) == -1) 80 { 81 perror("gettimeofday"); 82 exit(EXIT_FAILURE); 83 } 84 int64_t current = tv.tv_usec; 85 current += tv.tv_sec * 1000 * 1000; 86 return current; 87 }

我們?cè)诰€程訪問pInstance_之前加上了互斥變量, ?那么每次只能有一個(gè)線程訪問到該指針, 所以避免了new出多個(gè)Singleton對(duì)象導(dǎo)致內(nèi)存泄露的問題。 ??

但是這樣也導(dǎo)致了一個(gè)問題, ?每個(gè)線程都要搶到鎖以后才能判斷 是否需要?jiǎng)?chuàng)建一個(gè)?Singleton對(duì)象, 其中搶到鎖以后,其他線程都堵塞在mutex_那行代碼, 浪費(fèi)了大量的CPU系統(tǒng)資源。

?

3. 我們采用?DCLP(double-check-locking-pattern)來避免線程資源的浪費(fèi)。

?

1 #include <iostream> 2 #include "MutexLock.h" 3 #include "Thread.h" 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <string.h> 7 #include <sys/time.h> 8 using namespace std; 9 10 int64_t getUTime(); 11 12 //多線程下具有隱患 13 class Singleton 14 { 15 public: 16 static Singleton *getInstance() 17 { 18 if(pInstance_ == NULL) 19 { 20 mutex_.lock(); 21 if(pInstance_ == NULL) //線程的切換 22 pInstance_ = new Singleton; 23 mutex_.unlock(); 24 } 25 26 return pInstance_; 27 } 28 private: 29 Singleton() { } 30 31 static Singleton *pInstance_; 32 static MutexLock mutex_; 33 }; 34 35 Singleton *Singleton::pInstance_ = NULL; 36 MutexLock Singleton::mutex_; 37 38 class TestThread : public Thread 39 { 40 public: 41 void run() 42 { 43 const int kCount = 1000 * 1000; 44 for(int ix = 0; ix != kCount; ++ix) 45 { 46 Singleton::getInstance(); 47 } 48 } 49 }; 50 51 int main(int argc, char const *argv[]) 52 { 53 //Singleton s; ERROR 54 55 int64_t startTime = getUTime(); 56 57 const int KSize = 100; 58 TestThread threads[KSize]; 59 for(int ix = 0; ix != KSize; ++ix) 60 { 61 threads[ix].start(); 62 } 63 64 for(int ix = 0; ix != KSize; ++ix) 65 { 66 threads[ix].join(); 67 } 68 69 int64_t endTime = getUTime(); 70 71 int64_t diffTime = endTime - startTime; 72 cout << "cost : " << diffTime / 1000 << " ms" << endl; 73 74 return 0; 75 } 76 77 78 79 int64_t getUTime() 80 { 81 struct timeval tv; 82 ::memset(&tv, 0, sizeof tv); 83 if(gettimeofday(&tv, NULL) == -1) 84 { 85 perror("gettimeofday"); 86 exit(EXIT_FAILURE); 87 } 88 int64_t current = tv.tv_usec; 89 current += tv.tv_sec * 1000 * 1000; 90 return current; 91 }

以上代碼和2的不同在于, 搶鎖之前先進(jìn)行一次判斷pInstance_是否為空, 如果不為空那么不需要再搶鎖了直接return原指針, 避免堵塞。

那么為什么搶到鎖之后還有再判斷一次 該指針是否為空呢?? ?因?yàn)樵谧畛醯那闆r下(尚未new出一個(gè)對(duì)象時(shí)的零界條件下), 如果有2個(gè)線程同時(shí)通過了第一個(gè)判斷, 一個(gè)搶到鎖, 另一個(gè)被鎖堵塞,

如果鎖后面無判斷 指針是否為空, 依然導(dǎo)致 內(nèi)存泄露, 因?yàn)楸欢氯木€程在其后搶到鎖 依然會(huì)執(zhí)行new, 導(dǎo)致內(nèi)存泄露。

?

所以 采用?DCLP, 我們想象一下, 當(dāng)Single同被創(chuàng)建出來, pInstance_ == NULL 始終為假, 使判斷后面的 搶鎖和再判斷 短路,這樣既可以保證安全, 又保證了效率。

?

以上?

?

轉(zhuǎn)載于:https://www.cnblogs.com/DLzhang/p/4014389.html

總結(jié)

以上是生活随笔為你收集整理的C++学习之路: 单例模板的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。