標籤:
要想深入的理解STL的迭代器、分配器等,就必須瞭解C++模板編程中的一個技巧——Traits。
1、問題的提出
C++的模板特性為泛型程式設計提供了支援。這樣我們就可以編寫更加通用的代碼,而不必過分去關心參數的類型。然而事實卻是,類型的不同,很多時候卻影響到了演算法中的某個小小的實現。舉個標準庫裡的類string,wstring。
其實它們對應的是兩個模板,前者單字元,後者寬字元。
typedef basic_string<char, char_traits<char>, allocator<char> > string;typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> > wstring;
模板basic_string需要有一個得出字串長度的函數length,那麼問題就來了。因為char和wchar_t所對應的求長度API並不一樣。前者是strlen,後者是wcslen。
正是為瞭解決這樣類似的問題,C++中的traits技巧被提煉出來了。
2、解決方案
因為模板參數的類型不同,可能會影響到模板中具體的演算法,那麼我們就需要把這些與模板參數相差的方法從模板basic_string中提取出來,而保證basic_string演算法的一致不受參數類型不同的影響。而上面的char_traits模板即是把與模板參數相差的方法都封裝起來了。如果定義這樣一個模板.
template<class _Elem>struct char_traits{static void __CLRCALL_OR_CDECL assign(_Elem& _Left, const _Elem& _Right){ // assign an element_Left = _Right;}static bool __CLRCALL_OR_CDECL eq(const _Elem& _Left, const _Elem& _Right){ // test for element equalityreturn (_Left == _Right);}//……//……//…..static size_t __CLRCALL_OR_CDECL length(const _Elem *_First){ // find length of null-terminated sequence// _DEBUG_POINTER(_First);size_t _Count;for (_Count = 0; !eq(*_First, _Elem()); ++_First)++_Count;return (_Count);}};
這裡的legnth實現是一個通用演算法迴圈遍曆,並沒有使用系統的strlen,wcslen,效率相對低一些。那麼如果我一定要使用strlen,wcslen呢?
這裡就需要用到模板的特化,也即指定模板的參數類型。
// STRUCT char_traits<wchar_t> template<> struct char_traits<wchar_t> { // properties of a string or stream wchar_t element static void __CLRCALL_OR_CDECL assign(_Elem& _Left, const _Elem& _Right) { // assign an element _Left = _Right; } static bool __CLRCALL_OR_CDECL eq(const _Elem& _Left, const _Elem& _Right) { // test for element equality return (_Left == _Right); } …… …… ….. static size_t __CLRCALL_OR_CDECL length(const _Elem *_First) { // find length of null-terminated sequence // _DEBUG_POINTER(_First); return (::wcslen(_First)); } }; // STRUCT char_traits<char> template<> struct char_traits<char> { // properties of a string or stream wchar_t element static void __CLRCALL_OR_CDECL assign(_Elem& _Left, const _Elem& _Right) { // assign an element _Left = _Right; } static bool __CLRCALL_OR_CDECL eq(const _Elem& _Left, const _Elem& _Right) { // test for element equality return (_Left == _Right); } …… …… ….. static size_t __CLRCALL_OR_CDECL length(const _Elem *_First) { // find length of null-terminated sequence // _DEBUG_POINTER(_First); return (::strlen(_First)); } };
當實現了上面兩個特化的模板之後,在模板basic_string中,我們如果需要知道當前模板參數類型的字串長度時,只需要調用char_traits::length()就可以調用到正確的函數了。
3、總結
通過以上的案例,我們可以看出,具體的traits技巧非常簡單。也就是將因為模板形參(包括類型形參、非類型形參)不同而導致的不同,抽取到新的模板中去,然後通過模板的特化(全特化、偏特化均可,至少有一個模板形參不同即可)來分別實現其不同。 這一類的模板,都會在命名中加上traits以示區別,所以也會把運用這一類方法稱為C++的traits技術。traits技術更展現出了一種編程的思想,也即將相同的提出複用,將不同的部分通過介面來實現。將模板形參與基不同的實現綁定在一起,其實與設計模式中的狀態模式很相似,都體現出了相同的編程思想。只不過前者是編譯時間確定的,後者則是運行時確定的。
4、注意
- Boost中有這樣一個例子。
template< typename T > struct is_pointer{ static const bool value = false; };template< typename T > struct is_pointer< T* >{ static const bool value = true; };這樣我就可以通過is_pointer<T>::value來判斷當前類型是否為指標類型。
非類型模板形參
Template<bool b>Struct algo_sort{ Template<typename T> Static void sort(T& obj) { Quick_sort(obj); }}Template<>Struct algo_sort<true>{ Template<typename T> Static void sort(T& obj) { Select_sort(obj); }}這樣就能夠模板形參調用不同的排序方法了.
模板形參不僅僅與變數方法有關,還可能與類型有類.
template< typename T >struct STRUCT_TYPE{ typedef int MY_TYPE; typedef LONGLONG POWER_TYPE;};template<>struct STRUCT_TYPE<double>{ typedef float MY_TYPE; typedef double POW_TYPE;};template< typename T > struct STRUCT_ALGO{ // 下面的Typename是指示T::MY_TYPE是一個類型而不是成員變數 // 在VS2005中加與不加均可 typedef typename T::MY_TYPE myType; typedef T::POWER_TYPE powType; powType GetPow(const myType& value) { return value*value; }};這樣我們甚至可以將模板形參關聯的變數類型也可以抽離出來,以提高模板的通用性.
C++ Traits技術