最近在複習C++有關知識,又從新看<<Effective C++>>,收穫頗豐。原來之前看這邊書,好多地方都是淺嘗輒止。<<Effective C++>>條款25:考慮寫出一個不拋出異常的swap函數,涉及到C++模板專門化(Templates Specialization)和函數重載(overloading)問題,而當重載與模板攪合在一塊兒時,許多問題都變得「模棱兩可」。編程
首先回顧<<Effective C++>>條款25:考慮寫出一個不拋出異常的swap函數,想告訴咱們什麼東西。安全
swap函數爲咱們提供了異常安全編程的方法,以及用來做爲處理自我賦值一種常見機制,所以實現一個不拋出異常的swap函數,變得相對重要起來。缺省狀況下的swap函數的典型實現以下:app
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函數。ide
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特化版本。this
2)若是Widget與WidgetImpl不是class,而是class template,特化版本的swap函數,咱們可能想寫成這樣的形式:spa
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命名空間。code
--------------------------------------------------------------------華麗分割線--------------------------------------------------------------------對象
上面介紹的內容,涉及到如下的內容:1)模板函數;2)重載函數;3)全特化和偏特化。當這些東西交織在一塊兒的時候,咱們須要足夠的耐心作區分甄別。
1)模板類、模板函數與重載
// Example 1: Class vs. function template, and overloading // // A class template template<typename T> class X { /*...*/ }; // (a) 類模板 // A function template with two overloads template<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