模板基礎(chǔ)知識(shí)

閱讀經(jīng)典——《C++ Templates》01

  1. 函數(shù)模板
  1. 類模板
  2. 非類型模板參數(shù)
  3. 一些技巧
  4. 模板代碼的組織結(jié)構(gòu)

一、函數(shù)模板

定義

template <typename T>
inline T const& max (T const& a, T const& b)
{
  return a < b ? b : a;
}

使用

max(7, 1);
max(7.1, 1.2);
max("mathematics", "math");

編譯時(shí),函數(shù)模板根據(jù)實(shí)參來(lái)確定模板參數(shù)T的類型,針對(duì)每一種類型實(shí)例化出不同的函數(shù)。由于max中使用了比較運(yùn)算符operator<,因此類型T必須支持該操作,否則編譯器報(bào)錯(cuò)。

模板參數(shù)不支持自動(dòng)類型轉(zhuǎn)換,例如,下面的調(diào)用會(huì)出錯(cuò)。

max(7, 1.2);    //wrong

編譯器無(wú)法根據(jù)實(shí)參決定T的類型,因?yàn)?code>7和1.2是兩種不同的類型,這里不允許int自動(dòng)轉(zhuǎn)換為double。有一種妥協(xié)方案,顯式指定T的類型,這時(shí)int可以轉(zhuǎn)換為double。

max<double>(7, 1.2);

找錯(cuò)誤

下面的程序隱藏著一個(gè)驚天Bug,請(qǐng)把它找出來(lái)。

template <typename T1, typename T2>
inline T1 const& max (T1 const& a, T2 const& b)
{
    return a < b ? b : a;
}
max(4, 4.2);

答案見(jiàn)文末。

二、類模板

聲明和定義

template <typename T>
class Stack {
  private:
    std::vector<T> elems;
  public:
    Stack();
    void push(T const&);
    void pop();
    T top() const;
};

template <typename T>
void Stack<T>::push (T const& elem)
{
    elems.push_back(elem);
}
...

使用

Stack<int> intStack;
Stack<std::string> stringStack;
intStack.push(7);
stringStack.push("hello");

編譯時(shí),類模板根據(jù)模板參數(shù)實(shí)例化出相應(yīng)的類對(duì)象和成員變量,而成員函數(shù)并不一定實(shí)例化,只有那些被調(diào)用了的成員函數(shù)才會(huì)被實(shí)例化。顯然這樣做可以節(jié)省空間,而且,對(duì)于那些“未能支持所有成員函數(shù)中的所有操作”的類型,只要不調(diào)用那些不支持的成員函數(shù),就仍然可以使用。聽(tīng)起來(lái)有些抽象,舉個(gè)例子,假如Stack中也有max操作,那么如果使用自定義類型Person作為Stack的模板參數(shù),而且Person沒(méi)有重載operator<運(yùn)算符,那么該stack對(duì)象就不能訪問(wèn)max方法,若訪問(wèn)則編譯器會(huì)報(bào)錯(cuò)。

局部特化

可以指定類模板的特定實(shí)現(xiàn),并且要求某些模板參數(shù)仍然必須由用戶來(lái)定義。

例如類模板:

template <typename T1, typename T2>
class MyClass {
    ...
};

就可以有下面幾種局部特化:

//兩個(gè)模板參數(shù)具有相同的類型
template <typename T>
class MyClass<T, T> {
    ...
};
//第2個(gè)模板參數(shù)的類型是int
template <typename T>
class MyClass<T, int> {
    ...
};
//兩個(gè)模板參數(shù)都是指針類型
template <typename T1, typename T2>
class MyClass<T1*, T2*> {
    ...
};

缺省模板實(shí)參

可以為模板參數(shù)定義缺省值。例如,在Stack<>類中把用于存放元素的容器類型定義為第2個(gè)模板參數(shù),并使用std::vector<>作為缺省值。

template <typename T, typename CONT = std::vector<T> >
class Stack {
  private:
    CONT elems;
  ...
};

三、非類型模板參數(shù)

模板參數(shù)并不一定是屬于typename的類型,也可以是普通值,稱為非類型模板參數(shù)。例如,我們可以把棧容量MAXSIZE作為Stack<>的一個(gè)非類型模板參數(shù),并用它初始化數(shù)組大小。

template <typename T, int MAXSIZE>
class Stack {
  private:
    T elems[MAXSIZE];
  ...
};

使用方式如下:

Stack<int, 20> int20Stack;
Stack<int, 40> int40Stack;
Stack<std::string, 40> stringStack;

非類型模板參數(shù)只能是常整數(shù)(包括枚舉)或指向外部鏈接對(duì)象的指針。(關(guān)于外部鏈接對(duì)象的概念請(qǐng)參考...)

四、一些技巧

關(guān)鍵字typename
typename最初用于指定模板內(nèi)部的標(biāo)識(shí)符是一個(gè)類型,例如:

template <typename T>
class MyClass {
    typename T::SubType* ptr;
    ...
};

如果不加typename,SubType會(huì)被認(rèn)為是T的一個(gè)靜態(tài)成員,而不會(huì)被認(rèn)為是一個(gè)內(nèi)部類型。

成員模板
如果類的成員函數(shù)也是獨(dú)立的模板函數(shù),則稱之為成員模板。例如,給Stack<>類增加一個(gè)賦值操作符operator=成員模板函數(shù)。

//聲明
template <typename T>
class Stack {
  ...
  public:
    ...
    template <typename T2>
    Stack<T>& operator= (Stack<T2> const&);
};
//定義
...
template <typename T>
  template <typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
    ...
}

模板的模板參數(shù)
當(dāng)模板參數(shù)也是一個(gè)模板的時(shí)候,情況就變得復(fù)雜了。例如,Stack<T, CONT>中,模板參數(shù)CONT就是一個(gè)模板,我們需要傳入std::vector<T>作為實(shí)參。但是這樣做無(wú)法約束vector的模板參數(shù)TStack的第一個(gè)模板參數(shù)T一致,有可能出錯(cuò)。這種情況下使用模板的模板參數(shù)更合適。

template <typename T, template <typename ELEM> class CONT = std::deque>
class Stack {
  private:
    CONT<T> elems;
  ...
};

使用時(shí)不必傳入容器類的模板參數(shù),它會(huì)自動(dòng)根據(jù)Stack類的模板參數(shù)決定。

Stack<int> intStack;  //使用缺省模板參數(shù)
Stack<float, std::vector> floatStack;  //使用vector<float>作為容器

模板的模板參數(shù)只能使用class作為關(guān)鍵字,因?yàn)橹挥蓄惪梢宰鳛槟0宓哪0鍏?shù)。函數(shù)模板不支持模板的模板參數(shù)。

零初始化
未初始化的基本數(shù)據(jù)類型通常具有一個(gè)不確定(undefined)值。因此建議采用如下寫法:

template <typename T>
void foo()
{
    T x;  //不建議這樣寫,如果T是基本數(shù)據(jù)類型,那么x本身是一個(gè)不確定的值
    T x = T();  //建議這樣寫,如果T是基本數(shù)據(jù)類型,那么x是0或者false
}

五、模板代碼的組織結(jié)構(gòu)

我們通常把聲明寫在.h文件中,把定義寫在.cpp文件中。然而這種慣例被模板打破了。

如果把函數(shù)模板的聲明和定義分別寫在兩個(gè)文件中,鏈接器將會(huì)報(bào)錯(cuò),提示找不到函數(shù)的定義。這是因?yàn)椋瘮?shù)模板還沒(méi)有實(shí)例化,也就是說(shuō),函數(shù)模板的定義所在的文件并沒(méi)有被編譯,因?yàn)榫幾g器不知道應(yīng)該使用哪個(gè)模板參數(shù)來(lái)實(shí)例化。因此,通常的做法是,把函數(shù)模板的聲明和定義全部放在頭文件中。

//myfirst2.h
#ifndef MYFIRST_H
#define MYFIRST_H

#include <iostream>
#include <typeinfo>

//模板聲明
template <typename T>
void print_typeof(T const&);

//模板的實(shí)現(xiàn)/定義
template <typename T>
void print_typeof(T const& x)
{
    std::cout << typeid(x).name() << std::endl;
}

#endif

模板代碼的這種組織結(jié)構(gòu)稱為包含模型。除此之外,還有顯式實(shí)例化、分離模型等組織結(jié)構(gòu),但都不常用,特別是分離模型(使用export關(guān)鍵字導(dǎo)出模板)已經(jīng)被c++標(biāo)準(zhǔn)委員會(huì)廢除。

找錯(cuò)誤答案

template <typename T1, typename T2>
inline T1 const& max (T1 const& a, T2 const& b)
{
    return a < b ? b : a;
}
max(4, 4.2);

T1int,T2double,返回值類型為int。由于需要返回的數(shù)是double類型的b,因此需要一個(gè)從doubleint的轉(zhuǎn)換,這次轉(zhuǎn)換將會(huì)創(chuàng)建一個(gè)臨時(shí)int型變量作為返回值。而由于返回類型為引用,導(dǎo)致該函數(shù)返回后將持有一個(gè)臨時(shí)局部變量的引用,一旦臨時(shí)變量被釋放,繼續(xù)使用這個(gè)引用將得到意想不到的結(jié)果,甚至引起程序崩潰。

解決方案是返回值不使用引用。這個(gè)問(wèn)題很容易出現(xiàn),與此類似的還有返回局部變量的指針,因此編程時(shí)需要多加注意。

關(guān)注作者文集《C++ Templates》,第一時(shí)間獲取最新發(fā)布文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容