版權聲明 版權聲明:原創文章 禁止轉載 請通過右側公告中的“聯繫郵箱([email protected])”聯繫我 勿用於學術性引用。 勿用於商業出版、商業印刷、商業引用以及其他商業用途。 本文不定期修正完善。 本文鏈接:http://www.cnblogs.com/wlsandwho/p/ ...
=================================版權聲明=================================
版權聲明:原創文章 禁止轉載
請通過右側公告中的“聯繫郵箱([email protected])”聯繫我
勿用於學術性引用。
勿用於商業出版、商業印刷、商業引用以及其他商業用途。
本文不定期修正完善。
本文鏈接:http://www.cnblogs.com/wlsandwho/p/5371102.html
恥辱牆:http://www.cnblogs.com/wlsandwho/p/4206472.html
=======================================================================
其實Singleton模式,很多人都寫過類似的博客。
雖然閱讀量很多很多,可能超過了我所有博客的閱讀量,但是沒幾個的代碼是能用的。
而且也沒有說為什麼要用這種東西。
也沒說在什麼情況下要用這種東西。
本文不講解原理,詳細請參見四人幫的經典書籍。我相信原理的東西,我和大師還是有差距的。
這裡只講點自己的看法。
有什麼不對的地方歡迎指正。不喜歡來咬我呀。
=======================================================================
下麵王林森舉兩個最常見的例子,以說明為什麼要用Singleton模式。
=======================================================================
1 一個在各個文件里都需要用到的變數
作為一隻MFCer,以對話框程式為例,我們知道有一個叫theApp的實例。(什麼你不是?那這個例子和這篇文章的代碼不適合你。:)
這個東西在WLSApp.h中用extern聲明,
在WLSApp.cpp中定義。
theApp實例可以在CWLSDlg中使用。(因為.h文件有聲明,.cpp文件里有定義,實在想不通可以把.h和.cpp合成一個.hpp)
現在假如王林森在該對話框程式里又添加了另一個對話框CWLSDlg2,(用作其他彈出式界面),
那麼如果CWLSDlg2要使用theApp,
就必須在CWLSDlg2中引用WLSApp.h文件。
現在再假如王林森在程式初始化的時候讀取了一些配置用於稍後所有的界面初始化,比方說CMultiLanguage的一個實例oML用作多語言支持,
那麼如果想要所有的界面都使用同一個oML,
就需要像theApp一樣,在WLSApp.h中引用MultiLanguage.h,
在WLSApp.h中用extern聲明,
在CWLSApp.cpp中定義,
在所有打算使用oML的文件里,引用WLSApp.h
(講道理,其實不是必須在WLSApp.h文件里,之所以要這麼做,是因為跟theApp的定義聲明放在一起比較好。)
這是一個很繁瑣的過程,而且你不能保證在所有的地方使用的是同一個。(一旦繁瑣,就可能出錯。)
=======================================================================
2 一個在多線程環境中初始化並使用的變數
假如我們要處理一些數據,對記錄中間結果那個變數的初始化工作是其中一個線程做的,但如果其餘線程也空閑著,那麼可以幫助這個線程一起處理數據。
(這個例子可能不恰當。)
顯然我們需要保證大家用的是一個同一個變數,既不能對同一份數據生成多個變數,也不能用不同的變數處理同一份數據。
=======================================================================
上面是可能用到Singleton的情況。
=======================================================================
有一種情況,記住不要用Singleton,那就是:
需要使用同一個變數,
在一個文件里定義,(可以在其頭文件里聲明)
並且只在這個文件里使用。
因為:
這種情況就應該用全局變數啊。(在這個文件的開始定義一個全局變數。)
(什麼?老師說“XXXXX”?老師還說“XXXXX”?好,你贏了。)
=======================================================================
搞清楚了這些,我們再繼續進行。
=======================================================================
以下以多語言支持CMultiLanguage為例。
(用多語言支持來舉例,是因為我想把我的設計模式系列寫成有實際能用例子的系列,而不是那些做燒烤做遙控器之類的很奇怪的例子。)
常見的Singleton實現,有多種情況,這裡舉兩個:
1 在GetInstance里返回一個static CMultiLanguage oML。(這個要在.cpp文件里定義並初始化。屏蔽拷貝構造和賦值運算符等。)
2 在GetInstance里,通過判斷指針是否為空,返回一個通過new CMultiLanguage的poML。
坊間傳聞C++11可以在第1種的代碼里不加鎖,我不是很清楚,對此沒有探討過。(下麵討論加鎖。)
作為VS2010SP1用戶,我通常使用第2種。
=======================================================================
對於加鎖,好多博客里只寫了個Lock和UnLock,然後一個註釋“這裡加鎖”、“這裡解鎖”。
這種代碼你肯定跑不了的好嗎?!
還有的博客是用了內部類,利用構造函數加鎖,析構函數解鎖。這個想法很不錯,但是大多數的代碼也用不了。
因為你傳參了但是沒用到啊,你沒用到同一個臨界區怎麼知道是加了鎖了?
你怎麼保證你外部傳進來的作為參數的臨界區是同一個?怎麼保證這個臨界區的生命期?
另外個人用個人的鎖,那不叫加鎖啊。大家用同一把鎖那才叫加鎖啊。
所以,同時作為VS用戶和MFC用戶,我會使用一個CCriticalSection,把它typedef為CLock。
聲明一個static的CLock成員對象,在.cpp文件里定義。(這裡註意要有類名進行限定修飾,不然會不識別。)
判斷指針是否為空,加鎖,判斷指針是否為空,分配,解鎖。(坊間傳聞這個叫雙重加鎖?我覺得名字起的不好。)
搞定!
=======================================================================
再來說下垃圾回收。
可能有人會說這個變數的使用貫穿始終,不需要啊。
是的,如果能做到只在主程式/主界面里使用,還真的不需要,在整個程式結束的時候,任何資源都會被回收。
可是,如果這個變數是用在一個經常創建並銷毀的地方,那麼就要當心了。
比方說,變數定義在一個彈出的子對話框上,這個對話框有三個選項卡,它們共用這個變數。
不停的關閉和打開這個字對話框,會不斷的創建和銷毀變數。如果不回收記憶體,就會不斷的造成記憶體泄漏。直至記憶體耗盡然後一步一卡。
在垃圾回收上,比較贊同用內部類的方式處理。
使用一個內部類的static修飾的變數,例如static CAutoDeleter m_oAD,然後再在.cpp文件中定義並初始化。(因為大家都知道的原因,static是類擁有而不是變數擁有的。)
=======================================================================
大體的需要註意的地方就這些。
下麵來具體的舉一個實際的例子。
=======================================================================
多語言支持,是一個不可避免的話題。我們的征途是星辰大海,必然要走出國門,黑白頭鷹、駱駝、漢斯貓以及其他動物的。
然而在我們沒有走出國門前,經常會忽視掉/忘記這個功能模塊。
那麼問題來了:
後期如何添加?
怎麼能支持擴展語言?
把要做的發給外貿的同事,他們不會編程,能改嗎?(即一定要等外貿的同事翻譯完,我們工程師再去修改成能夠用的形式嗎?)
直擊靈魂的三個問題!
王林森這文章的後半部分,將使用Singleton模式,使用簡單易行的方法,解決這個問題。
=======================================================================
在開始我的方法之前,先看看現在都有哪些做法。
1 資源DLL。很顯然,這需要編程能力。
2 .ini配置文件。這個網上大多的例子,在後期界面修改引入新的ID的時候,比較麻煩。
值得一提的是,微軟在VS的示例里,給出了一個些多語言支持的例子。(我64位預設安裝路徑,好像在打SP1補丁包後,位於C:\Program Files (x86)\Microsoft Visual Studio 10.0\Samples\2052)
在VC2010Samples\C++\International里有三個例子。可以看下。
其中,
IME跟顯示多語言支持無關。
unires用了String Table,調用LoadString來顯示多種文字。寫死了,不好改。
satdll這個Solusion有問題。
坑啊!
=======================================================================
好了,忘掉剛纔黑暗的小路,王林森即將帶領你走向光明的,呃,小道。
我們應當有一個解決方案,這個解決方案能夠支持動態的多語言切換——這個不太重要,
支持語言集的拓展——這個重要,因為後期可能要加其他語言,
支持支持界面的和非界面的多語言文字——這個重要,因為除了界面,你可能還要生成報表,報表也得改啊。
順便說下,多語言的本質是“根據用戶的選擇,提供不同的字元串”。
=======================================================================
配置文件分兩種,
第一種,預覽文件。在這個文件里,列出當前選用的是哪個語言,
一共有多少種語言,
每種語言的ID,
每種語言的名字,
每種語言的文件名。
可以按照這樣的形式:
1 [CurrentSetting] 2 CurrentLanguageID=2 3 4 [LanguageSum] 5 Sum=2 6 7 [Language1] 8 LanguageID=1 9 LanguageName=中文 10 LanguageFileName=Chinese.ini 11 12 [Language2] 13 LanguageID=2 14 LanguageName=English 15 LanguageFileName=English.ini
配置文件的第二種,語言數據文件。
借鑒resource.h文件,王林森把每一個用到的字元串都分配一個字元串ID。
考慮到既有界面和控制項是有資源ID的,
把資源ID和字元串ID都用
1 ID_MAIN_DLG_CAPTION 王林森的多語言支持示常式序 2 ID_STRING_HELLO 你好
的形式寫在配置文件里。
這裡註意一點。必須保證所有的ID_XXX_XXX不重名。
題外話,MFC程式里,不同類型控制項的ID重名不影響,是因為.rc文件里指明瞭控制項的類型。這裡我們只是一個配置文件,為了簡化並和諧統一,採用不重名的方式。
可以參照下麵的形式:
中文
1 [LanguageText] 2 IDC_STATIC_PROVERB=當蝴蝶在南半球拍了兩下翅膀,它就會稍微飛高一些。 3 IDC_BUTTON1=禁止轉載 4 IDC_STATIC1=王林森 5 IDC_RADIO1=半瓶以上 6 IDC_RADIO2=一瓶未滿
英文
1 [LanguageText] 2 IDC_STATIC_PROVERB=When the butterfly in the southern hemisphere beat the wings, it will fly a little higher. 3 IDC_BUTTON1=No reproduction without permission. 4 IDC_STATIC1=wlsandwho 5 IDC_RADIO1=Oooops 6 IDC_RADIO2=I can't translate this sentence into English.
那麼問題來了,類如何知道在配置文件里有哪些ID的?
王林森在往配置文件寫ID的時候,順便用SetKey的方式,向一個容器註冊一個ID名。
即在從無到有的創建一個語言數據文件時,
每寫一行
1 ID_XXXXXX=XXX XXX
就在VS里寫一行代碼
1 m_oML.SetKey(TEXT("ID_XXXXXX"));
當然可以讀取每一個配置文件,用解析文本文件的每一行的方式,得到“=”左邊的鍵的文本,然後添加到一個set里,代替手工註冊。這樣就更簡單了。
在王林森的示例代碼中,會綜合使用這兩種方法。
=======================================================================
讀寫配置文件的函數參見MSDN。函數有很多。我只用三個。
GetPrivateProfileInt
GetPrivateProfileString
WritePrivateProfileString
前兩個讀數字和字元串,最後一個寫字元數據。
現在要做的就是,讀取預覽文件,然後根據預覽文件數據去讀取語言數據文件。
是的,在一次初始化中,讀取所有的配置文件。反正初始化也要時間,再多這半秒也無所謂。這方便後面動態切換語言。
=======================================================================
實際上,王林森使用了map生成了兩個字典。
第一個是預覽字典,存放的是預覽文件的數據。
第二個是語言數據字典,存放的是眾多的語言數據文件的數據。
=======================================================================
這樣通過
使用ID的文本作為Key查找,得到目標文本
在界面處使用操作ID的API來使用文本/在非界面處,直接使用文本
的方式完成了多語言支持。
=======================================================================
看一下效果
切換前
切換後
=======================================================================
部分代碼。
頭文件:
1 #pragma once 2 //wlsandwho 3 4 #include <atlstr.h> 5 #include <vector> 6 #include <map> 7 #include <set> 8 class CMultiLanguage 9 { 10 ////////////////////////////////////////////////////////////////////////// 11 public: 12 typedef struct _LanguagePreviewData 13 { 14 int nID; 15 CString strName; 16 CString strFileName; 17 18 bool operator <(const _LanguagePreviewData &oDR) const 19 { 20 return nID<oDR.nID; 21 } 22 23 _LanguagePreviewData& operator = (const _LanguagePreviewData & oDR) 24 { 25 nID=oDR.nID; 26 strName=oDR.strName; 27 strFileName=oDR.strFileName; 28 29 return *this; 30 } 31 32 }LanguagePreviewData,*PLanguagePreviewData; 33 34 typedef std::vector<LanguagePreviewData> VctLanguagePreview; 35 36 typedef std::vector<CString> VctStrKey; 37 38 typedef std::map<CString,CString> MapStrId2Text; 39 typedef std::map<CString,CString>::iterator ItMapStrId2Text; 40 41 typedef std::map<int,MapStrId2Text> MapLanguageId2Text; 42 typedef std::map<int,MapStrId2Text>::iterator ItMapLanguageId2Text; 43 typedef std::set<CString> SetKeys; 44 45 ////////////////////////////////////////////////////////////////////////// 46 public: 47 void RegisterIDasKey(CString strKey); 48 bool InitialMultiLanguageSupport(CString strPath); 49 50 int GetCurrentLanguageID(); 51 bool ChangeLanguage(int nLanguageID); 52 53 CString GetStringbyID( CString strStrID ); 54 55 VctLanguagePreview GetLanguagePreview(); 56 bool IsOnSupport(); 57 58 protected: 59 CString GetFilePath(); 60 bool AutoRegisterIDasKey(CString strFileName); 61 62 bool ReadLanguagePreview(CString strFileName); 63 bool ReadLanguageData(); 64 65 protected: 66 SetKeys m_setKeys; 67 VctStrKey m_vctStrKey; 68 69 bool m_bSupport; 70 int m_nCurrentLanguageID; 71 72 CString m_strFileName; 73 74 VctLanguagePreview m_vctLanguagePreview; 75 MapLanguageId2Text m_mapLanguageId2Text; 76 77 ////////////////////////////////////////////////////////////////////////// 78 public: 79 ~CMultiLanguage(void); 80 81 protected: 82 CMultiLanguage(void); 83 CMultiLanguage(const CMultiLanguage&); 84 CMultiLanguage& operator=(const CMultiLanguage&); 85 86 ////////////////////////////////////////////////////////////////////////// 87 public: 88 static CMultiLanguage* GetInstance(); 89 90 protected: 91 static CMultiLanguage* m_pInstance; 92 93 ////////////////////////////////////////////////////////////////////////// 94 private: 95 class CAutoDeleter 96 { 97 public: 98 ~CAutoDeleter() 99 { 100 if (CMultiLanguage::m_pInstance) 101 { 102 delete CMultiLanguage::m_pInstance; 103 CMultiLanguage::m_pInstance=NULL; 104 } 105 } 106 }; 107 ////////////////////////////////////////////////////////////////////////// 108 private: 109 typedef CCriticalSection CLock; 110 ////////////////////////////////////////////////////////////////////////// 111 private: 112 static CLock m_oLock; 113 static CAutoDeleter m_oAutoDeleter; 114 };
源文件:
1 #include "StdAfx.h" 2 3 #include "Multilanguage.h" 4 #include "afx.h" 5 6 CMultiLanguage::CMultiLanguage(void) 7 { 8 m_nCurrentLanguageID=1; 9 m_bSupport=false; 10 11 m_strFileName=TEXT(""); 12 13 m_vctLanguagePreview.clear(); 14 m_setKeys.clear(); 15 m_vctStrKey.clear(); 16 m_mapLanguageId2Text.clear(); 17 } 18 19 CMultiLanguage::~CMultiLanguage(void) 20 { 21 m_vctLanguagePreview.clear(); 22 m_setKeys.clear(); 23 m_vctStrKey.clear(); 24 m_mapLanguageId2Text.clear(); 25 } 26 27 CString CMultiLanguage::GetStringbyID( CString strStrID ) 28 { 29 CString strRes=TEXT("wlsandwho"); 30 31 ItMapLanguageId2Text itLId2Text=m_mapLanguageId2Text.find(m_nCurrentLanguageID); 32 if (itLId2Text!=m_mapLanguageId2Text.end()) 33 { 34 ItMapStrId2Text itStrId2Text=itLId2Text->second.find(strStrID); 35 if (itStrId2Text!=itLId2Text->second.end()) 36 { 37 strRes=itStrId2Text->second; 38 } 39 } 40 41 return strRes; 42 } 43 44 bool CMultiLanguage::ChangeLanguage( int nLanguageID ) 45 { 46 m_oLock.Lock(); 47 //Change the current language text. 48 m_nCurrentLanguageID=nLanguageID; 49 50 //Save the configure. 51 CString strCurrentLanguageID; 52 strCurrentLanguageID.Format(TEXT("%d"),m_nCurrentLanguageID); 53 54 WritePrivateProfileString(TEXT("CurrentSetting"),TEXT("CurrentLanguageID"),strCurrentLanguageID,m_strFileName); 55 56 m_oLock.Unlock(); 57 58 return true; 59 } 60 61 bool CMultiLanguage::InitialMultiLanguageSupport(CString strFileName) 62 { 63 bool bMade = true; 64 65 m_oLock.Lock(); 66 //bMade = ReadLanguagePreview(strFileName) ? AutoRegisterIDasKey(m_vctLanguagePreview[0].strFileName) && ReadLanguageData() && (m_bSupport = true) : m_bSupport = false; 67 68 if (ReadLanguagePreview(strFileName)) 69 { 70 int nFilesNumber = m_vctLanguagePreview.size(); 71 72 for (int i = 0; i < nFilesNumber; i++) 73 { 74 if (!AutoRegisterIDasKey(m_vctLanguagePreview[i].strFileName)) 75 { 76 bMade = false; 77 break; 78 } 79 } 80 81 bMade=ReadLanguageData(); 82 } 83 else 84 { 85 bMade = false; 86 } 87 88 m_bSupport = bMade; 89 90 m_oLock.Unlock(); 91 92 return bMade; 93 } 94 95 bool CMultiLanguage::ReadLanguagePreview( CString strFileName ) 96 { 97 bool bOK=true; 98 99 m_nCurrentLanguageID=GetPrivateProfileInt(TEXT("CurrentSetting"),TEXT("CurrentLanguageID"),0,strFileName); 100 if (m_nCurrentLanguageID==0) 101 { 102 bOK=false; 103 } 104 105 m_strFileName=strFileName; 106 107 int nLanguageSum=0; 108 nLanguageSum=GetPrivateProfileInt(TEXT("LanguageSum"),TEXT("Sum"),0,strFileName); 109 110 CString strLanguageNo=TEXT("Language"); 111 CString strTempLanguageNo; 112 113 TCHAR szReturnString[MAX_PATH]; 114 115 for (int i=1;i<=nLanguageSum;i++) 116 { 117 strTempLanguageNo.Format(TEXT("%d"),i); 118 strTempLanguageNo=strLanguageNo+strTempLanguageNo; 119 120 LanguagePreviewData stLPD; 121 stLPD.nID=GetPrivateProfileInt(strTempLanguageNo,TEXT("LanguageID"),0,strFileName); 122 123 memset(szReturnString,0,MAX_PATH); 124 GetPrivateProfileString(strTempLanguageNo,TEXT("LanguageName"),TEXT("wlsandwho"),szReturnString,MAX_PATH,strFileName); 125 stLPD.strName=CString(szReturnString); 126 127 memset(szReturnString,0,MAX_PATH); 128 GetPrivateProfileString(strTempLanguageNo,TEXT("LanguageFileName"),TEXT("wlsandwho"),szReturnString,MAX_PATH,strFileName); 129 stLPD.strFileName=CString(szReturnString); 130 131 m_vctLanguagePreview.push_back(stLPD); 132 } 133 134 if (m_vctLanguagePreview.size()<1) 135 { 136 bOK = false; 137 } 138 139 return bOK; 140 } 141 142 CString CMultiLanguage::GetFilePath() 143 { 144 TCHAR szPath[MAX_PATH]; 145 GetCurrentDirectory(MAX_PATH, szPath); 146 CString strPath = szPath + CString(TEXT("\\")); 147 148 return strPath; 149 } 150 151 bool CMultiLanguage::ReadLanguageData() 152 { 153 int nSize=m_vctLanguagePreview.size(); 154 if (nSize<1) 155 { 156 return false; 157 } 158 159 TCHAR szReturnString[MAX_PATH]; 160 161 CString strPath=GetFilePath(); 162 163 for (int i=0;i<nSize;i++) 164 { 165 m_vctLanguagePreview[i].strFileName; 166 167 memset(szReturnString,0,MAX_PATH); 168 169 int nKeySize=m_vctStrKey.size(); 170 171 MapStrId2Text mapTempStrId2Text; 172 for (int j=0;j<nKeySize;j++) 173 { 174 GetPrivateProfileString(TEXT("LanguageText"),m_vctStrKey[j],TEXT("wlsandwho"),szReturnString,MAX_PATH,strPath+m_vctLanguagePreview[i].strFileName); 175 mapTempStrId2Text.insert(std::make_pair(m_vctStrKey[j],CString(szReturnString))); 176 } 177 178 m_mapLanguageId2Text.insert(std::make_pair(m_vctLanguagePreview[i].nID,mapTempStrId2Text)); 179 } 180 181 return true; 182 } 183 184 bool CMultiLanguage::AutoRegisterIDasKey(CString strFileName) 185 { 186 CStdioFile oStdFile; 187 188 int nPos = -1; 189 CString strTemp; 190 BOOL bFile=oStdFile.Open(GetFilePath()+TEXT("\\")+strFileName, CFile::modeRead); 191 if (bFile==FALSE) 192 { 193 return false; 194 } 195 196 while (oStdFile.ReadString(strTemp)) 197 { 198 nPos=strTemp.Find(TEXT("=")); 199 if (nPos!=-1) 200 { 201 strTemp=strTemp.Left(nPos); 202 m_setKeys.insert(strTemp); 203 } 204 } 205 206 oStdFile.Close(); 207 208 VctStrKey vctTempStrKey(m_setKeys.begin(), m_setKeys.end()); 209 210 m_vctStrKey = vctTempStrKey; 211 212 return true; 213 } 214 215 void CMultiLanguage::RegisterIDasKey( CString strKey ) 216 { 217 m_vctStrKey.push_back(strKey); 218 } 219 220 int CMultiLanguage::GetCurrentLanguageID()