模板偏特化 指針 模板萃取 指針

C++相關語法基礎

模板特化(template specialization)

通用模板對於某種特定的類型可能是錯誤的,所以可能需要對特定的類型單獨定義模板的實現。另一方面,可以利用特定類型的特點進行優化。

模板特化是指這樣一個定義,該定義指定一個或多個模板的形參實際類型或值。 形式如下:

  • template<>
  • 模板名(可以是函數或類)加一對尖括號,尖括號中指定模板形參的實際類型。
  • 函數形參表和函數體或類定義。

示例:

1
2
3
4
5
6
7
8
9
10
11
template <class T1, class T2>
class some_template
{
  // ...
};

template <>
class some_template<string, int>
{
  // ...
};

類模板的部分特化(partial specialization)

部分特化是指針對template參數更進一步的條件限制所涉及出來的一個特化版本。 這裏的更進一步的條件限制並不僅限於指定模板形參的實際類型。 編譯器在實例化時,優先使用特化的版本。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <class T1, class T2>
class some_template
{
  // ...
};

template <class T1>
class some_template<T1, int>
{
  // ...
};

// 條件限制並不僅限於指定模板形參的實際類型
template <class T1, class T2>
class some_template<T1, T2*>
{
  // ...
};

迭代器關聯類型

type_value

在使用容器時,我們經常會用到容器內部元素的類型,C++本身並不能獲取變量類型。大多數算法的參數都是一些迭代器的範圍,算法本身並不知道迭代器所指對象的內容。即使通過 typeid() 來獲取,得到的也只能是類型名稱,不能用來定義變量。

一個簡單的解決方法是:迭代器的內部定義一個內部類型。

1
2
3
4
5
6
7
8
9
10
template <class T>
struct MyIter
{
  typedef T value_type;
  // ...
};

template <class T>
typename I::value_type func(I iter)
{ retrun *iter; };

STL容器都會定義 value_type 作爲迭代器所指對象的類型。我們可以通過 iterator_traits 來提取這個類型。

1
2
3
4
5
6
template <class I>
struct iterator_traits
{
  // 這裏就是value_type的定義
  typedef typename I::value_type value_type; 
};

注意上面的typename關鍵字,因爲I是一個模板參數,在它被實例化之前,編譯器並不知道value_type是個啥。所以必須顯式地告訴編譯器這是一個類型。

當我們需要知道迭代器所指對象的類型時,只需要用 typename iterator_traits<I>::value_type 即可。

但是原生的指針也是一種迭代器,它並不是一個類,我們也不能在其內部定義一個類型。 這個時候我們就需要利用C++的模板偏特化特性了。iterator_traits還有一個偏特化的版本,這個版本用來處理原生指針。

1
2
3
4
5
template <class T>
struct iterator_traits<T*>
{  
  typedef T value_type;
};

這裏雖然沒有直接指定特化的類型,但是進一步對類型進行了約束,即規定實參類型必須是指針,所以也是一種偏特化。

這樣,我們可以使用 typename iterator_traits<int*>::value_type 得到int類型。

除此之外,還有一個版本用來處理常指針,如 const int* ,使得得到的結果是int,而不是const int。

1
2
3
4
5
template <class T>
struct iterator_traits<const T*>
{  
  typedef T value_type;
};

第一部分 爲什麼要有萃取技術

既然是一種智能指針,iterator也要對一個原生指針進行封裝,而問題就源於此,當我們需要這個原生指針所指對象的類型的時候(例如聲明變量),怎麼辦呢?

Case1 對於函數的局部變量

這種情況我們可以採用模版的參數推導,例如:

template <class T> void func(T iter)

如果T是某個指向特定對象的指針,那麼在func中需要指針所指向對象類型的變量的時候,怎麼辦呢?這個還比較容易,模板的參數推導機制可以完成任務,如下:

template <class T, class U>

void func_impl(T t, U u) {

    U temp; // OK, we’ve got the type

            // The rest work of func…

}

template <class T>

void func(T t) {

    func_impl(t, *t); // forward the task to func_impl

}

通過模板的推導機制,我們輕而易舉的或得了指針所指向的對象的類型,但是事情往往不那麼簡單。例如,如果我想把傳遞給func的這個指針參數所指的類型作爲返回值,顯然這個方法不能湊效了,這就是我們的case 2。

Case2 對於函數的返回值

儘管在func_impl中我們可以把U作爲函數的返回值,但是問題是用戶需要調用的是func,於是,你不可能寫出下面的代碼:

template <class T, class U>

U func_impl(T t, U u) {

    U temp; // OK, we’ve got the type

            // The rest work of func…

}

template <class T>

(*T) func(T t) { // !!!Wrong code

    return func_impl(t, *t); // forward the task to func_impl

}

 

int i  =10;

cout<<func(&i)<<endl; // !!! Can’t pass compile

紅色的部分概念上如此正確,不過所有的編譯器都會讓你失望。這個問題解決起來也不難,只要做一個iterator,然後在定義的時候爲其指向的對象類型制定一個別名,就好了,像下面這樣:

template <class T>

struct MyIter {

    typedef T value_type; // A nested type declaration, important!!!

    T* ptr;

    MyIter(T* p = 0) : ptr(p) {}

    T& operator*() const { return *ptr; }

};

而後只要需要其指向的對象的類型,只要直接引用就好了,例如:

template <class I>

typename I::value_type func(I iter) { return *iter; }

很漂亮的解決方案,看上去一切都很完美。但是,實際上還是有問題,因爲func如果是一個泛型算法,那麼它也絕對要接受一個原生指針作爲迭代器,但是顯然,你無法讓下面的代碼編譯通過:

int *p = new int(52);

cout<<func(p)<<endl; // !!!Is there a int::value_type?? Wrong Code here

我們的func無法支持原生指針,這顯然是不能接受的。此時,template partial specialization就派上了用場。

Solution :template partial specialization是救世主

既然剛纔的設計方案仍不完美,那我們就再加一個間接層,把智能指針和原生指針統統的封裝起來。在討論之前,先要澄清一下template partial specialization的含義。所謂的partial specialization和模板的默認參數是完全不同的兩件事情,前者指的是當參數爲某一類特定類型的時候,採用特殊的設計,也就是說是“針對template參數更進一步的條件限制所設計出來的一個特化版本”;而默認參數則是當不提供某些參數的時候,使用的一個缺省。

參考:partial specialization的語法

Template <typename T> class C<T*> {} // 爲所有類型爲T*的參數而準備的特殊版本

好了,下面我們就找一個專職的負責人,用來封裝迭代器封裝的對象類型。首先,把我們剛纔的MyIter重新包裝一下:

template <class I>

struct iterator_traits {

    Typedef I::value_type value_type;

}

現在,我們的func又有了新的面貌:

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

    return *ite;

}

儘管這次我們的函數返回值的長度有些嚇人,但是,我們的確爲原生指針找到了好的解決方案。只要爲原生指針提供一個偏特化的iterator_traits就OK了。如下:

template <class I>

struct iterator_traits<T*> {

    typedef T value_type;

};

下面,我們終於可以讓func同時支持智能指針和原生指針了:

template <class I>

struct iterator_traits {

    Typedef I::value_type value_type;

}

template <class T>

struct iterator_traits<T*> {

    typedef T value_type;

};

 

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

    return *ite;

}

 

int main() {

    MyIter<int> iter = new int(520);

    int *p = new int(520);

 

    // This time the following two statements will success

    cout<<func(iter)<<endl;

    cout<<func(p)<<endl;

    return 0;

}

但是,我們離最後的成功還有最後一步,如果,我們需要聲明一個value_type類型的左值,但是卻給iterator_traits傳遞了一個const int*,顯然結果有問題,於是,爲const T*也另起爐竈,準備一份小炒:

template<class T>

struct iterator_traits<const T*> {

    typedef T value_type;

}

OK ,現在萬事大吉,無論是正宗迭代器,原生指針,const原生指針,我們都可以利用iterator_traits萃取出其封裝的對象的類型,萃取技術由此而來。


發佈了707 篇原創文章 · 獲贊 48 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章