神奇的traits
介紹traits的文章很多,但感覺大部分文章的說明都很晦澀難懂,把一個并不很復雜的C++模板的應用描述的過于復雜。忍不住想把自己的理解跟大家分享一下,或許我也只是掌握了一點traits的皮毛而已,但也希望這些皮毛能略微抓住你的眼球,帶給你一些啟發。
首先,介紹traits前,回味一下C++的模板及應用,如果你腦海里浮現出的只是為實現一些函數或類的重用的簡單模板應用,那我要告訴你,你out了。最近在整理一些模板的應用方式,有時間的話會寫出來分享給大家,本文不會去詳細討論traits以外的模板的各種高級應用。 那么,言歸正傳,什么是traits?其實它并不是一個新的概念,上個世紀90年代中期就已經被提出,只是到了這個世紀才在各個C++庫中被廣泛使用,而我也是在這個概念誕生十多年后才接觸到它。C++之父Bjarne Stroustrup對traits有如下的描述: Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details".
我不知道官方或一些書上是如何去解釋traits的,我的理解是: 當函數,類或者一些封裝的通用算法中的某些部分會因為數據類型不同而導致處理或邏輯不同(而我們又不希望因為數據類型的差異而修改算法本身的封裝時),traits會是一種很好的解決方案。
本以為能很簡單的描述它,誰知道還是用了如此長的句子才說明清楚,相當的慚愧。大家只要有個大概的概念就ok了,甚至即使完全沒概念也沒關系,下面會通過實際代碼來說明。
先看這樣一個例子。如果有一個模板類Test: [cpp]?view plaincopy template?<typename?T>?? class?Test?{?? ?????......?? };??
假設有這樣的需求,類Test中的某部分處理會隨著類型T的不同而會有所不同,比如希望判斷T是否為指針類型,當T為指針類型時的處理有別于非指針類型,怎么做? 模板里再加個參數,如下? [cpp]?view plaincopy template?<typename?T,?bool?isPointer>?? class?Test?{?? ?????......//?can?use?isPointer?to?judge?whether?T?is?a?pointer?? };??
然后用戶通過多傳一個模板類型來告訴Test類當前T是否為指針。(Test<int*, true>) 很抱歉,所有的正常點的用戶都會抱怨這樣的封裝,因為用戶不理解為什么要讓他們去關心自己的模板類型是否為指針,既然是Test類本身的邏輯,為什么麻煩用戶呢? 由于我們很難去限制用戶在使用模板類時是使用指針還是基本數據類型還是自定義類型,而用常規方法也沒有很好的方法去判斷當前的T的類型。traits怎么做呢? 定義traits結構: [cpp]?view plaincopy template?<typename?T>?? struct?TraitsHelper?{?? ?????static?const?bool?isPointer?=?false;?? };?? template?<typename?T>?? struct?TraitsHelper<T*>?{?? ?????static?const?bool?isPointer?=?true;?? };??
也許你會很困惑,結構體里就一個靜態常量,沒有任何方法和成員變量,有什么用呢?解釋一下,第一個結構體的功能是定義所有TraitsHelper中isPointer的默認值都是false,而第二個結構體的功能是當模板類型T為指針時,isPointer的值為true。也就是說我們可以如下來判斷當前類型: TraitsHelper<int>::isPointer值為false, 可以得出當前類型int非指針類型 TraitsHelper<int*>::isPointer值為true, 可以得出當前類型int*為指針類型 也許看到這里部分人會認為我簡直是在說廢話,請再自己品味下,這樣是否就可以在上面Test類的定義中直接使用TraitsHelper<T>::isPointer來判斷當前T的類型了。 [cpp]?view plaincopy if?(TraitsHelper<T>::isPointer)?? ?????......?? else?? ?????......??
再看第二個例子: 還是一個模板類Test: [cpp]?view plaincopy template?<typename?T>?? class?Test?{?? public:?? ?????int?Compute(int?d);?? private:?? ?????T?mData;?? };??
它有一個Compute方法來做一些計算,具有int型的參數并返回int型的值。 現在需求變了,需要在T為int類型時,Compute方法的參數為int,返回類型也為int,當T為float時,Compute方法的參數為float,返回類型為int,而當T為其他類型,Compute方法的參數為T,返回類型也為T,怎么做呢?還是用traits的方式思考下。 [cpp]?view plaincopy template?<typename?T>?? struct?TraitsHelper?{?? ?????typedef?T?ret_type;?? ?????typedef?T?par_type;?? };?? template?<>?? struct?TraitsHelper<int>?{?? ?????typedef?int?ret_type;?? ?????typedef?int?par_type;?? };?? template?<>?? struct?TraitsHelper<float>?{?? ?????typedef?float?ret_type;?? ?????typedef?int?par_type;?? };??
然后我們再把Test類也更新下: [cpp]?view plaincopy template?<typename?T>?? class?Test?{?? public:?? ?????TraitsHelper<T>::ret_type?Compute(TraitsHelper<T>::par_type?d);?? private:?? ?????T?mData;?? };??
可見,我們把因類型不同而引起的變化隔離在了Test類以外,對用戶而言完全不需要去關心這些邏輯,他們甚至不需要知道我們是否使用了traits來解決了這個問題。 到這里,再讓我們回過來取品味下開始我說的那句話: 當函數,類或者一些封裝的通用算法中的某些部分會因為數據類型不同而導致處理或邏輯不同時,traits會是一種很好的解決方案。
首先,介紹traits前,回味一下C++的模板及應用,如果你腦海里浮現出的只是為實現一些函數或類的重用的簡單模板應用,那我要告訴你,你out了。最近在整理一些模板的應用方式,有時間的話會寫出來分享給大家,本文不會去詳細討論traits以外的模板的各種高級應用。 那么,言歸正傳,什么是traits?其實它并不是一個新的概念,上個世紀90年代中期就已經被提出,只是到了這個世紀才在各個C++庫中被廣泛使用,而我也是在這個概念誕生十多年后才接觸到它。C++之父Bjarne Stroustrup對traits有如下的描述: Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details".
我不知道官方或一些書上是如何去解釋traits的,我的理解是: 當函數,類或者一些封裝的通用算法中的某些部分會因為數據類型不同而導致處理或邏輯不同(而我們又不希望因為數據類型的差異而修改算法本身的封裝時),traits會是一種很好的解決方案。
本以為能很簡單的描述它,誰知道還是用了如此長的句子才說明清楚,相當的慚愧。大家只要有個大概的概念就ok了,甚至即使完全沒概念也沒關系,下面會通過實際代碼來說明。
先看這樣一個例子。如果有一個模板類Test: [cpp]?view plaincopy
假設有這樣的需求,類Test中的某部分處理會隨著類型T的不同而會有所不同,比如希望判斷T是否為指針類型,當T為指針類型時的處理有別于非指針類型,怎么做? 模板里再加個參數,如下? [cpp]?view plaincopy
然后用戶通過多傳一個模板類型來告訴Test類當前T是否為指針。(Test<int*, true>) 很抱歉,所有的正常點的用戶都會抱怨這樣的封裝,因為用戶不理解為什么要讓他們去關心自己的模板類型是否為指針,既然是Test類本身的邏輯,為什么麻煩用戶呢? 由于我們很難去限制用戶在使用模板類時是使用指針還是基本數據類型還是自定義類型,而用常規方法也沒有很好的方法去判斷當前的T的類型。traits怎么做呢? 定義traits結構: [cpp]?view plaincopy
也許你會很困惑,結構體里就一個靜態常量,沒有任何方法和成員變量,有什么用呢?解釋一下,第一個結構體的功能是定義所有TraitsHelper中isPointer的默認值都是false,而第二個結構體的功能是當模板類型T為指針時,isPointer的值為true。也就是說我們可以如下來判斷當前類型: TraitsHelper<int>::isPointer值為false, 可以得出當前類型int非指針類型 TraitsHelper<int*>::isPointer值為true, 可以得出當前類型int*為指針類型 也許看到這里部分人會認為我簡直是在說廢話,請再自己品味下,這樣是否就可以在上面Test類的定義中直接使用TraitsHelper<T>::isPointer來判斷當前T的類型了。 [cpp]?view plaincopy
再看第二個例子: 還是一個模板類Test: [cpp]?view plaincopy
它有一個Compute方法來做一些計算,具有int型的參數并返回int型的值。 現在需求變了,需要在T為int類型時,Compute方法的參數為int,返回類型也為int,當T為float時,Compute方法的參數為float,返回類型為int,而當T為其他類型,Compute方法的參數為T,返回類型也為T,怎么做呢?還是用traits的方式思考下。 [cpp]?view plaincopy
然后我們再把Test類也更新下: [cpp]?view plaincopy
可見,我們把因類型不同而引起的變化隔離在了Test類以外,對用戶而言完全不需要去關心這些邏輯,他們甚至不需要知道我們是否使用了traits來解決了這個問題。 到這里,再讓我們回過來取品味下開始我說的那句話: 當函數,類或者一些封裝的通用算法中的某些部分會因為數據類型不同而導致處理或邏輯不同時,traits會是一種很好的解決方案。
總結
- 上一篇: C++中的四类强制转换?
- 下一篇: C语言中| 按位或, || 逻辑或。