標籤:常見 turn version 彙編指令 重載函數 cpp microsoft c函數 cout
把曾經寫的C++模板的應用心得發表出來。
回憶起當時在學習C++模板時的無助和恐懼,如今還心有餘悸。我分享出來我的心得,僅僅希望別人少走彎路,事實上它就這麼幾種使用方法,不須要害怕。
我總結了模板的四種使用方式,基本覆蓋了大部分的模板使用情境,瞭解了這四種方式。就能夠在看其他代碼時理解別人為什麼會在這個地方用模板。
模板的四大情境1.資料類型與演算法相分離的泛型程式設計
2.類型適配Traits
3.函數轉寄
4.元編程
1.資料類型與演算法相分離的泛型程式設計在模板編程中,最常見的就是此類使用方法。將資料類型與演算法相分離,實現泛型程式設計。
STL本身實現了資料容器與演算法的分離,而STL中大量的模板應用,則實現了資料類型與容器演算法的分離,它是泛型程式設計的一個典範。
如:
std::vector<int>std::vector<long>
單件的模板實現,將單件的演算法和單件的類型相分離。
如:
template <class T> class Singleton { protected: Singleton(){} public: static T& GetInstance() { static T instance; return instance; } }; Class CMySingleton : public Singleton< CMySingleton >
資料類型與演算法的分離是最easy理解的一種使用情境。我認為這可能也是發明泛型演算法的初衷。
2.類型適配TraitsC++教科書一定會提到C++語言的多態性。我對多態的理解就是:同樣的方法產生了不同的行為。這在C++中最常見的用例就是虛函數,虛函數被子類覆蓋後由子類重寫,不同的子類對於同樣的虛函數調用表現出不同的行為,但調用者絲毫不關心詳細的實現,它僅僅對於虛介面進行調用完事。
這樣的多態就是執行時的多態。由於它是在執行時才知道終於調用到哪個子類函數上。
與執行時多態相對,另有一種多態形式是藉助於模板實現的。模板同意我們使用單一的泛型標記,來關聯不同的特定行為:但這樣的關聯是在編譯期進行處理的,這些藉助於模板的多態稱為靜多態 。請看下方的示範範例。
class A1 { public: void fun(); }; class A2 { public: void fun(); }; template<typename A> class CFunInvoker { public: Static void invoke(A* t) { t->fun(); } } A1 a1; A2 a2; CFunInvoker<A1>::invoke(&a1); CFunInvoker<A2>::invoke(&a2);
A1,A2兩個類。都有一個fun的函數。還有一個調用者CFunInvoker須要調用這兩個類的fun函數。
上面這個範例。A1和A2並沒有什麼關聯,它們只須要提供一個名為fun參數為空白的函數就能夠被調用了。而調用者CFunInvoker對於被調用者的要求也就是有這樣一個函數即可。只能過約定好函數名和參數的方式就能夠實現對A1,A2。CFunInvoker 差點兒全然的解耦。
假設用動多態實現的話,那就須要A1和A2繼承自同一個含有虛介面fun的父類(比方這個父類叫CFunBase)。而且對於CFunInvoker來說。它須要定義一個這種父類指標(CFunBase*)。並對其進行調用。這個時候,A1和A2就不那麼自由了。不論什麼對CFunBase的改動都會影響到A1和A2的功能。這樣A1。A2,CFunInvoker的耦合性變高了。它們須要的是一個類來實現關聯。
因此,靜多態的優點就是:靜多態不須要實現多態的類型有公用的基類。由於它能夠一定程度上的解耦。可是它仍然須要模板類與模板參數之間有一些協議(這裡協議就比方上面的範例中須要名為fun參數為空白的函數)。
但假設有些模板參數類型不滿足這些協義。怎麼辦?比方我想調用CFunInvoker<int>::invoke但int類型又提供不了一個名為fun參數為空白的函數。
因此我們引入靜多態的還有一個用處:Traits(粹取)
比方以下這個Host類須要模板參數類型提供一個叫dosomething的方法。所以Host<A>是能夠編譯通過,但Host<int>是編譯只是的
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3l4aXNncmVhdA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" style="font-size:18px">
為瞭解決問題。我們添加一個Traits類,它一定會對外提供一個dosomething的方法。對於普通類型,它就轉寄這種方法,於對int型,它作了特化。實現了一個空的dosomething的方法。因此不管是Host<Traits<A>> 還是Host<Traits<int>>,都能夠通過編譯
STL中大量運用了traits。比方我們常見的string類型,別以為它僅僅能處理字串,它能夠處理不論什麼類型,你甚至能夠用它來處理二進位的buffer(binaryarray)。
比方我們能夠改動std::string讓其內部處理long類型,讓它成為一個long型數組。
typedef basic_string<long, char_traits<long>, allocator<long> > longstring;longstring strlong;strlong.push_back(23);strlong.push_back(4562);long arrLong[2] = {23, 4562};longstring strlongFromArr(arrLong, ARRAYSIZE(arrLong));assert(strlong == strlongFromArr);
3.函數轉寄
模板類的非常多應用在於它能針對不同的模板參數產生不同的類。
這使得我們能夠通過模板類將函數指標以及它的參數類型記錄下來。在須要的時候再對函數進行調用。
基於函數轉寄的應用有非常多
- boost::function
- boost::signal slot
- 模板實現的C++託付
- 模板實現的C++反射
…………
凡是涉及到把函數指標存放起來。進行延遲調用的情況,都能夠應用函數轉寄
以下類比一個簡單的轉寄
template<typename T> class function;template<typename R, typename A0> class function <R (A0)>{ public: typedef R(*fun)(A0 ); function(fun ptr):m_ptr(ptr){} R operator()(A0 a) {(*m_ptr)(a);} fun m_ptr; }; int testfun(int a) { printf("%d", a); return 2;} function<int (int)> f1(&testfun);f1(4);上面的範例把函數testfun的函數指標,以及它的函數簽名int (int)作為模板參數儲存在了f1這個對象中。在須要的時候,就能夠用f1對這個函數進行調用。
以下的範例類比了類成員函數的轉寄
<pre name="code" class="cpp">template<class T> class function; template<typename R, typename A0, typename T> class function<R (T::*)(A0) > { public: typedef R(T::*fun)(A0); function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){} R operator()(A0 a) {(m_pThis->*m_ptr)(a);} fun m_ptr; T* m_pThis; }; class CA{public:void Fun(int a) {cout << a;}};CA a;function<void (CA::*)(int)> f(&CA::Fun, &a);f(4); // 等價於a.Fun(4);
上面的範例把class CA的對象指標,成員函數指標。以及它的成員函數簽名
void (CA::*)(int)
作為模板參數儲存在了f這個對象中。在須要的時候,就能夠用f對這個對象的這個成員函數函數進行調用。
調用的方式非常easy
f(4); // 等價於a.Fun(4);
就像是調用一個普通的C函數一樣。CA類對象不見了,.或->操作符不見了。函數轉寄實現了一層層的封裝與綁定。終於上調用者與CA類型隔離,實現瞭解耦。
只是函數轉寄的這樣的封裝使會使得調用效率減少。怎樣讓封裝後的調用像普通函數調用一樣快,請參考我發的還有一篇學習心得
高效C++託付的原理
4.元編程很多書介紹元編程是這樣說的:Metaprogram:a program about a program。就是“一個關於還有一個程式的程式”。這方面介紹非常多。
從一個示範範例入手:一段從1累加到100的程式
主模板有一個整形的參數N。 主模板中的枚舉值value取值會取得模板參數為N-1的模板類的value值,加上自身的N值。
然後為N=1的時候特化處理value=1。
這樣在GetSum<100>這個類中它的value值就是5050。這個值不是在執行時候電腦算的,而是在編譯時間編譯器已經算好了。這麼長的C++代碼終於編譯出來的結果就和僅僅寫一句
prinft("%d",5050);
產生的彙編指令是一樣的。
從這個小範例能夠總結出元編程的思想:
在編譯期實現對類型或數值的計算。
利用模板特化機制實現編譯期條件選擇結構,利用遞迴模板實現編譯期迴圈結構,模板元程式則由編譯器在編譯期解釋運行。
在編譯期我們能夠用來協助計算的工具有:
- 模板的特化
- 函數重載決議
- typedef
- static類型變數和函數
- sizeof,
- =,:?。-。+,<, >運算子
- enum
1。元編程中特化使用方法一般用特化實現條件的推斷。
包含普通if的推斷
迴圈條件終結推斷
。
。
。
。。
以下是一個範例
struct is_void { enum{value = false;} } template<> struct is_void<void> { enum{value = true;} } std::cout << is_void<int> //顯示false
上面這個範例能夠用來推斷一個類型是不是void類型
2。元編程中函數重載決議使用方法以下這個範例來自於《C++設計模式新思維》用來推斷兩個類型之間是否有轉化關係
<pre name="code" class="cpp">template <class T, class U> struct Conversion { static char Test(U); static long Test(...); static T MakeT(); enum { exists = (sizeof(Test(MakeT())) == sizeof(char) )}; }; class A;class B: public A;printf("%d, %d", Conversion<B*, A*>::exists, Conversion<A*, B*>::exists); 輸出1,0
上面的範例通過重載決議和sizeof取得重載函數Test的返回值大小,再通過枚舉常量exists在編譯期儲存。在
Conversion<B*, A*>
中,重載決議採用的是char Test(A*)方法,因此Conversion<B*,A*>::exists為1。
而在
Conversion<A*, B*>
中,重載決議採用的是long Test(...)方法,因此Conversion<A*,B*>::exists為0。
3。
元編程中typedef使用方法在元編程中,typedef主要用來形成編譯期的類型資料結構。
最經典的TypeList結構
boost::tuple結構也是基於類似TypeList的結構
Boost的mpl庫中還實現了vector map set等資料結構
比方以下的示範範例就實現了一個ClassA=>ClassB=>ClassC的類型鏈表。
typedef struct NULL_TYPE{} NullType template<typename T, typename U = NullType> struct Typelist { typedef T Head; typedef U Tail; } typedef Typelist<ClassA, Typelist< ClassB, Typelist< ClassC, NullType>>> mytypelist ;
這個類型鏈表只唯獨類型資訊,有什麼用呢?我們能夠改造一下,給它添加兩個對象,形成一個能夠把不同類型元素存到一個鏈表中的對象鏈表
template<typename T, typename U = NullType> struct Typelist { typedef T Head; typedef U Tail; Head m_head;Tail m_tail;} Typelist<ClassA, Typelist <ClassB>> storage;Storage. m_head = ClassA();Storage.m_tail.m_head = ClassB();
這樣鏈表就存了ClassA和ClassB的兩個執行個體對象。
以下舉一個運用typelist強大威的的執行個體。
typelist實現簡單工廠
非常多時候我們會涉及到對象工廠, 這是簡單原廠模式的一種,就是依據需求產生不同的類對象。以下就是一個簡單工廠的範例。這樣的代碼隨處可見於各種C++項目。
void * CreateObj(const std::string & strClsName) { if (strClsName == “ClassA") { return new ClassA(); } else if (strClsName == " ClassB") { return new ClassB(); } else if (strClsName == " ClassC") { return new ClassC(); } }
這就是一個分支結構,假設類型特別多的話,代碼就會非常長非常挫。我們能夠用typelist來幫我們產生這種代碼。
這是一種高大上的方法
class ClassA{ public;virtual const char* getClassName(){ return m_classname;}static char* m_classname; //每一個類型用一個字串來表示自己的型別};char* ClassA::m_classname = “ClassA”;class ClassB …class ClassC …typedef Typelist<ClassA, Typelist< ClassB, Typelist< ClassC> > > mytypelist ; template<typename T, typename U> struct Typelist { typedef T Head; typedef U Tail; static void* CreatObj(const char *pName) { if (strcmp(Head::m_classname, pName) == 0 ) { return new Head; //找到相應的類 } else { return Tail::CreatObj(pName );//這裡就是對Typelist進行了遞迴調用。從而產生了分支代碼 } } };template<typename T> struct Typelist<T, NullType >//特化用以遞迴結束條件{ static void* CreatObj(const char *pName) { if (strcmp(Head::m_classname, pName) == 0 ) { return new Head; } else { return NULL; } } };ClassA* pa = (ClassA* )mytypelist:: CreatObj(“ClassA”);ClassB* pb = (ClassB* )mytypelist:: CreatObj(“ClassB”);ClassC* pc = (ClassC* )mytypelist:: CreatObj(“ClassC”);…
這樣的方式並沒有降低終於產生的彙編指令級的if else的數量。可是它不須要我們寫那麼多的if else了。通過模板的遞迴方式,讓編譯器自己主動為我們產生分支推斷。
動態類型建立已經由前面的類工廠實現了,如今我們能夠用類似的方面實現動態類型識別RuntimeClass主要有雙方面的功能:
1.動態類型建立
ClassA* pObj = CreateObj(“ClassA”);
2.動態類型識別
pObj ->IsKindOf( ClassA::GetRuntimeClass() ) ;
在MFC中,IsKindOf 方法是通過遍曆繼承鏈來確定是否屬於某種類型。一看到這樣的遍曆或迴圈的方式。我們就能夠考慮用模組遞迴來實現
以下是實現代碼。僅僅需利用前面講到的Conversion模板
template<typename T, typename U = NullType> struct Typelist { typedef T Head; typedef U Tail; template<typename SuperClass> static bool IsKindOf(const char *pName) { if (strcmp(Head:: getClassName(), pName) == 0 ) { return Conversion<Head*, SuperClass*>::exists; } else { return Tail::IsKindOf<SuperClass>(pName ); } } }; class ClassA;class ClassB : public Class A;class ClassC;ClassA* pa = new ClassA;ClassB* pb = new ClassB;typedef Typelist<ClassA, Typelist< ClassB, Typelist< ClassC>> > mytypelist ; printf(“%d, %d, %d,%d”, mytypelist::IsKindOf< ClassA >(pa->getClassName()),mytypelist::IsKindOf< ClassB >(pa->getClassName()), mytypelist::IsKindOf< ClassC >(pa->getClassName()), mytypelist::IsKindOf< ClassA >(pb->getClassName())); // 結果是 1,0,0,1
元編程技術非常多。比方還有數值運算等(最簡單的1到100累加的範例),我這裡僅僅是掛一漏萬。詳細能夠參考《C++設計模式新思維》。
C++ 範本應用淺析