閱讀經(jīng)典——《C++ Templates》01
- 函數(shù)模板
- 類模板
- 非類型模板參數(shù)
- 一些技巧
- 模板代碼的組織結(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ù)T與Stack的第一個(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);
T1為int,T2為double,返回值類型為int。由于需要返回的數(shù)是double類型的b,因此需要一個(gè)從double到int的轉(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ā)布文章。