1. 程式人生 > 實用技巧 >c++11模板的特化

c++11模板的特化

前面定義的Stack中,第二個模板引數要求支援backpush_backpop_back等介面。如果我們已經有一個自定義的容器Array,它的定義如下:

template<typename T, typename Allocator = std::allocator<T>>
struct Array
{
    void put(size_t index, const T& t);
    T get(size_t index);
    ...
};

Array只有put和get介面,並輔助以index引數進行元素存取。為了讓Array也能參與實現Stack,我們可以對Stack進行特化,當Stack的第二個引數是Array時重新定義Stack的實現:

template<typename T>
struct Stack<T, Array>
{
    Stack() : size(0)
    {
    }

    void push(const T& elem)
    {
        elems.put(size++, elem);
    }

    T pop()
    {
        if(empty()) throw std::out_of_range("Stack<>::pop: empty!");
        return elems.get(--size);
    }

    bool empty() const
    {
        return size == 0;
    }

private:
    size_t size;
    Array<T> elems;
};

在Stack特化版本的宣告template<typename T> struct Stack<T, Array>中,Stack名字後面的尖括號Stack<T, Array>中傳遞的引數可以是具體型別,也可以不是具體型別,但是至少要比Stack主模板(非特化版本)的引數更加具體一些,而且和主模板的引數宣告順序和約束必須一致。

如果特化版本中,所有的模板引數都被替換成了具體型別,那麼就叫做全特化,例如:

template<> 
struct Stack<int*, Array>
{
    ...
};

如果引數中還有非具體型別,那麼就叫做部分特化

或者偏特化,例如:

template<typename T>
struct Stack<T, Array>
{
    ...
};

無論是全特化還是偏特化,特化版本的宣告仍然需要使用關鍵字template,後面緊跟的尖括號中宣告特化版本中還在使用的非具體型別形參。由於全特化不再存在非具體型別,所以尖括號中為空,但是不能省略,皆以template <>開頭。

注意,主模板的template關鍵字後面定義了該模板的基本原型特徵,特化模板的模板名稱關鍵字後面的尖括號中的模板引數必須和主模板template關鍵字後面尖括號中的引數順序和約束一致。上例中由於主模板宣告第一個模板引數是型別,第二個模板引數是模板,所以特化版本Stack<T, Array>尖括號中的引數不能多也不能少,且順序不能顛倒,而且第二個引數模板Array的定義必須和主模板中對Container的模板約束一致。

特化版本的template後面緊跟的尖括號中僅是宣告特化版本中還在使用的非具體型別引數,和主模板template後面緊跟的尖括號中的引數沒有任何關係。

上例中,我們修改了Stack<T, Array>pushpopempty成員方法的實現,並且增加了size資料成員。我們甚至還可以修改Stack<T, Array>中的介面名稱,不再叫push和pop,或者刪掉empty的實現,只要Stack<T, Array>的客戶正確地使用該特化版本的介面即可。可見對於模板的特化,只需要其簽名(模板名和模板引數)和主模板保持一致,而對於其實現,和主模板以及其它特化版本的實現沒有任何關係,完全可以根據該特化版本的需要進行定製。

當我們給一個模板傳遞引數後,編譯器會從主模板和所有的特化版本的實現中進行選擇,簡單來說選擇的規則和函式過載的選擇順序類似,就是選擇最多匹配的那個版本。

如上例中如論是Stack<int, Array>還是Stack<double, Array>都會匹配template<typename T> struct Stack<T, Array>版本的實現,而Stack<int, std::list>則會匹配主模板的實現。

關於特化最後再提一個我們後面會用到的知識點,那就是模板可以被巢狀地定義在一個類中或模板中,但是模板的特化不可以。