最近在復習C++有關知識,又重新看<<Effective C++>>,收獲頗豐。原來以前看這邊書,好多地方都是淺嘗輒止。<<Effective C++>>條款25:考慮寫出一個不拋出異常的swap函數,涉及到C++模板專門化(Templates?Specialization)和函數重載(overloading)問題,而當重載與模板攪合在一起時,許多問題都變得“模棱兩可”。
首先回顧<<Effective C++>>條款25:考慮寫出一個不拋出異常的swap函數,想告訴我們什么東西。
swap函數為我們提供了異常安全編程的方法,以及用來作為處理自我賦值一種常見機制,因此實現一個不拋出異常的swap函數,變得相對重要起來。缺省情況下的swap函數的典型實現如下:
namespace std {template<typename T>void swap(T& a, T& b){T temp(a);a = b;b = temp; } }
然后,對于模型數據類型其成員變量是指針,指向一個對象,保存了數據(pointer to implementation手法)。如果copying函數采用了deep copying方法,上面的代碼將會非常低效,因為,只需要互換a與b指針即可。問題是,缺省版本swap對類型無法可知這些信息,因此針對上述情況,需要專門化swap函數。
1)如果T是class,可以先讓T提供一個swap函數,完成swap功能,然后借由functhon template的全特化,實現專門化的swap函數:
class Widge { public:void swap(Wiget& other){using std::swap();swap(pImpl, other.pImpl);}
private:
WidetImpl* pImpl;
};
//為程序提供一個特化版本的swap:
namespace std
{
template<>
void swap<Widegt>(Widget& a, Widget& b)
{ a.swap(b);
}
}
上面的代碼很好的與STL容器保持了一致性,因為STL容器也都提供了swap成員函數和std::swap特化版本。
2)如果Widget與WidgetImpl不是class,而是class template,特化版本的swap函數,我們可能想寫成這樣的形式:
namespace std {template<class T>void swap<Widegt<T>>(Widget<T>& a, Widget<T>& b) {a.swap(b);} }
然而這個代碼卻無法通過編譯,C++不支持function template的偏特化,我們需要使用模板函數的重載技術:
namespace std {template<class T>void swap( Widget<T>& a, Widget<T>& b) //重載了function templates {a.swap(b);} }
問題似乎已經解決了,嗯,是的,還存在一個問題:用戶可以全特化std內的templates,但是不能新的對象(template、function、class)。解決方法是將這些類與swap函數放到新的命名空間中,這邊便獨立與std命名空間。
--------------------------------------------------------------------華麗分割線--------------------------------------------------------------------
上面介紹的內容,涉及到以下的內容:1)模板函數;2)重載函數;3)全特化和偏特化。當這些東西交織在一起的時候,我們需要足夠的耐心做區分甄別。
1)模板類、模板函數與重載
// Example 1: Class vs. function template, and overloading //// A class templatetemplate<typename T> class X { /*...*/ }; // (a) 類模板// A function template with two overloadstemplate<typename T> void f( T ); // (b) 函數模板 template<typename T> void f( int, T, double ); // (c) 函數模板重載
(a)、(b)、(c)均沒有專門化,這些未被專門化的template又被稱為基礎基模板。
2)特化
template class可以有全特化與偏特化兩種, template function僅能全特化。
// Example 1, continued: Specializing templates // // A partial specialization of (a) for pointer types template<typename T> class X<T*> { /*...*/ }; // A full specialization of (a) for int template<> class X<int> { /*...*/ };// A separate base template that overloads (b) and (c) // -- NOT a partial specialization of (b), because // there's no such thing as a partial specialization // of a function template! template<class T> void f( T* ); // (d)// A full specialization of (b) for int template<> void f<int>( int ); // (e)// A plain old function that happens to overload with // (b), (c), and (d) -- but not (e), which we'll // discuss in a moment void f( double ); // (f)
當function template與重載攪合在一起的時候,就存在匹配哪個版本函數的問題,匹配規則如下:
1)首先查找non template function ,如果在這些函數中匹配成功,則匹配結束(first-class citizens)
2)否定,在base template function 中查找最匹配的函數,并實例化,如果base template function恰巧有提供全特化版本模板函數,則使用全特化版本(sencond-class citizens)
?將以上兩個規則運用的例子:
// Example 1, continued: Overload resolution // bool b; int i; double d;f( b ); // calls (b) with T = bool f( i, 42, d ); // calls (c) with T = int f( &i ); // calls (d) with T = int f( i ); // calls (e) f( d ); // calls (f)
最后一個問題:如何判斷哪個base template function被specialization,再看下面的例子:
// Example 2: Explicit specialization // template<class T> // (a) a base template void f( T );template<class T> // (b) a second base template, overloads (a) void f( T* ); // (function templates can't be partially // specialized; they overload instead) template<> // (c) explicit specialization of (b) void f<>(int*);// ...int *p; f( p ); // calls (c)
c是b是全特化,f(p)將會調用,符合人們的一般想法,但是,如果置換b與c的順序,結果就不那么一樣了:
// Example 3: The Dimov/Abrahams Example // template<class T> // (a) same old base template as before void f( T );template<> // (c) explicit specialization, this time of (a) void f<>(int*);template<class T> // (b) a second base template, overloads (a) void f( T* );// ...int *p; f( p ); // calls (b)! overload resolution ignores // specializations and operates on the base // function templates only
這個時候,c將是a的全特化(編譯器沒看到后面的b的定義)。按照配對規則,首先查找base template function最適合匹配的,b正好最為匹配,并且沒有全特化版本,因此將會調用b。
重要準則:
1)如果我們希望客戶化base template function,直接利用傳統的函數形式,如果使用重載形式,那么請不要提供全特化版本。
2)如果正在編寫一個base template function,不要提供特化和重載版本,將客戶化定制功能下放給用戶。實現方法是,在class template 同static 函數接口:
// Example 4: Illustrating Moral #2 // template<class T> struct FImpl;template<class T> void f( T t ) { FImpl<T>::f( t ); } // users, don't touch this! template<class T> struct FImpl { static void f( T t ); // users, go ahead and specialize this };
準則2的動機就是利用class template 特化與偏特化功能實現function 特化與偏特化功能。
?
參考文獻:
<<Effective C++>>, Scott Meyers
<<Why Not Specialize Function Templates?>>,??C/C++ Users Journal, 19(7), July 2001
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
?