C++模板專門化與重載

  最近在複習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

相關文章
相關標籤/搜索