模板別名
在C++98/03裡,我們可以通過typedef 關鍵字定義一個類型的別名,比如 typedef unsigned int uint_t;在這裡我們定義了unsigned int類型的別名和uint_t,在以後需要使用unsigned int的時候我們都可以用uint_t替換,但是uint_t僅僅是作為unsigned int的一個別名,如下的定義是不合法的:
typedef unsigned int uint_t;void func(unsigned int);void func(uint_t);
上面的func函數是一個不合法的函數重載,雖然使用typedef定義一個類型的別名很方便,但是typedef在使用上存在一些限制,比如說typedef無法重定義一個模板的別名。
考慮下面例子,我們在實際編程中經常使用到STL中的MAP,我們MAP的string類型的資料作為MAP的key,我們想根據STRING類型的KEY映射為一個String,int,long等類型的資料.
typedef std::map<std::string, int> map_t;//...typedef std::map < std::string, std::string > map_str;//...
如果需要映射成10中類型的資料,我們就需要利用typedef定義10個具體類型的別名,但是考慮到MAP的key值始終是變的,我們是否像下面一樣可以用typedef+模板來定義一個別名呢
template <typename T>typedef std::map<std::string, T> map;map<int> map_i;map<std::string> map_str;
遺憾的是上述的定義不能通過編譯,也就是C++ 98/03並不支援這樣的操作,而通常是通過一個包裹類的方式來實現上述的需求:
template <typename T>struct alias_map{typedef std::map<std::string, T> map;};alias_map<int>::map map_t;alias_map<int>::map map_str;
通過包裹類的方法雖然可以實現上述的需求,但是一看代碼就覺得,這個代碼可讀性差,不就是定義一個變數嗎。還需要整一個包裹類來封裝下,增加代碼裡不說,看著都煩,小幸運的是C++11終於讓你可以不用通過上述這種臃腫的方式來實現這個需求了。C++11中,新增了一個特性就是可以通過使用using來為一個模板定義別名,比如說上述的需求,使用C++11就可以這樣實現:
template <typename T>using alias_map = std::map < std::string, T > ;alias_map<int> map_t;alias_map<std::string> map_str;
系不系看著舒服很多啊,頓時神清氣爽,在C++11中,允許使用using關鍵字為一個模板來定義別名,實際上using包含了typedef的所有功能,來看下使用using關鍵字和typedef關鍵字定義普通類型別名的用法。
typedef unsigned int uint_t;using uint_t = unsigned int;typedef std::map<std::string, int> map_t;using map_t = std::map < std::string, int > ;
可以看到在對普通類型的別名定義上,兩種方法的使用基本等效,唯一不同的僅僅是定義的文法,using使用起來就像是賦值,但是在定義函數函數指標的時候,using看起來可讀性要稍微好一點,比如:
typedef void(*func)(int, int);using func = void(*)(int, int);
可能突然看起來使用using的方式來定義一個函數指標有點怪,但是習慣了之後會發現使用using這種賦值的方式更適用開發人員的思考方式。下面再顯示一個通過typedef和using方式分別來定義一個函數模板的例子:
template<typename T>struct FuncSt{typedef void(*func)(T, T);};FuncSt<int>::func func_typedef;template<typename T>using func_using = void(*func)(T, T);func_using<int> func_using;可以看到通過using定義模板別名的文法,僅僅是在普通類型別名文法基礎上增加了template參數列表,通過using可以輕鬆的建立一個模板的別名,而不需要像C++98/03那樣增加一個包裹類。但是需要額外注意的是使用using或者typedef僅僅是定義一個別名,不會創造新類型。
函數的預設範本參數
在C++98/03裡,類模板是支援預設的模板參數的,比如:
template<class T, class U = int, U n= 0>struct Foo{//};但是在C++98/03中確實不能支援函數模板的預設範本參數:
template <typename T = int> //error in C++98/03 default template argumentsvoid func(void){//...}現在這個限制在C++11中已經解除,上述的定義在C++11中可以直接使用了。在函數模板中當所有模板參數都有預設參數時,函數的調用就如同普通的函數調用,但是對於類末班而言,哪怕所有模板參數都有預設建構函式在使用時還是必須在模板名後跟隨<>來執行個體化。
C++11中函數的預設範本參數在使用規則上和其他的預設參數也有一些區別,普通函數的預設參數必須寫在參數列表的最後,而函數的模板參數就沒有這個限制,因此當使用預設範本參數和模板參數自動推導時就顯示十分靈活,可以指定函數中的一部分參數是預設參數,另一部分採用自動推導。比如:
template <typename R = int, typename U>R func(U val){//...}int _tmain(int argc, _TCHAR* argv[]){func(123);return 0;}但是如果在使用函數模板時如果顯示指定模板的參數,由於模板參數的填充順序是自左向右的,因此像下面這樣的調用返回的類型是long類型:
func<long>(123); //func傳回型別是填滿類型long
這個細節雖然簡單,但是在多個預設範本參數和多個模板參數自動推導穿插使用時會容易被忽略掉,造成使用上的一些意外,建議在使用的時候盡量還是價格預設範本參數寫在模板參數的末尾;另外當預設範本參數和自動參數推導同時使用時,若函數模板無法推匯出參數類型時,編譯器將使用預設範本參數,否則將使用自動推導的參數類型。這個跟函數的預設參數使用規則是一致的,比較好理解。
模板別名以及預設範本參數是在泛型程式設計中的一些小細節,是C++11對C++98/03一些細節上的提升,因此介紹的篇幅不多,主要是在使用的時候若可以的話可以通過這些小技巧增加代碼可讀性,減少代碼冗餘。