閱讀經(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ù)模板根據(jù)實(shí)參來確定模板參數(shù)T的類型猾瘸,針對每一種類型實(shí)例化出不同的函數(shù)。由于max
中使用了比較運(yùn)算符operator<
,因此類型T
必須支持該操作,否則編譯器報(bào)錯惕鼓。
模板參數(shù)不支持自動類型轉(zhuǎn)換,例如唐础,下面的調(diào)用會出錯呜笑。
max(7, 1.2); //wrong
編譯器無法根據(jù)實(shí)參決定T
的類型夫否,因?yàn)?code>7和1.2
是兩種不同的類型,這里不允許int
自動轉(zhuǎn)換為double
叫胁。有一種妥協(xié)方案,顯式指定T
的類型汞幢,這時int
可以轉(zhuǎn)換為double
驼鹅。
max<double>(7, 1.2);
找錯誤:
下面的程序隱藏著一個驚天Bug,請把它找出來森篷。
template <typename T1, typename T2>
inline T1 const& max (T1 const& a, T2 const& b)
{
return a < b ? b : a;
}
max(4, 4.2);
答案見文末输钩。
二、類模板
聲明和定義:
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");
編譯時仲智,類模板根據(jù)模板參數(shù)實(shí)例化出相應(yīng)的類對象和成員變量买乃,而成員函數(shù)并不一定實(shí)例化,只有那些被調(diào)用了的成員函數(shù)才會被實(shí)例化钓辆。顯然這樣做可以節(jié)省空間剪验,而且,對于那些“未能支持所有成員函數(shù)中的所有操作”的類型前联,只要不調(diào)用那些不支持的成員函數(shù)功戚,就仍然可以使用。聽起來有些抽象似嗤,舉個例子啸臀,假如Stack
中也有max
操作,那么如果使用自定義類型Person
作為Stack
的模板參數(shù)烁落,而且Person
沒有重載operator<
運(yùn)算符乘粒,那么該stack
對象就不能訪問max
方法,若訪問則編譯器會報(bào)錯伤塌。
局部特化:
可以指定類模板的特定實(shí)現(xiàn)灯萍,并且要求某些模板參數(shù)仍然必須由用戶來定義。
例如類模板:
template <typename T1, typename T2>
class MyClass {
...
};
就可以有下面幾種局部特化:
//兩個模板參數(shù)具有相同的類型
template <typename T>
class MyClass<T, T> {
...
};
//第2個模板參數(shù)的類型是int
template <typename T>
class MyClass<T, int> {
...
};
//兩個模板參數(shù)都是指針類型
template <typename T1, typename T2>
class MyClass<T1*, T2*> {
...
};
缺省模板實(shí)參:
可以為模板參數(shù)定義缺省值寸谜。例如竟稳,在Stack<>
類中把用于存放元素的容器類型定義為第2個模板參數(shù),并使用std::vector<>
作為缺省值熊痴。
template <typename T, typename CONT = std::vector<T> >
class Stack {
private:
CONT elems;
...
};
三他爸、非類型模板參數(shù)
模板參數(shù)并不一定是屬于typename
的類型,也可以是普通值果善,稱為非類型模板參數(shù)诊笤。例如,我們可以把棧容量MAXSIZE
作為Stack<>
的一個非類型模板參數(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ù)(包括枚舉)或指向外部鏈接對象的指針纪他。(關(guān)于外部鏈接對象的概念請參考...)
四、一些技巧
關(guān)鍵字typename:
typename最初用于指定模板內(nèi)部的標(biāo)識符是一個類型晾匠,例如:
template <typename T>
class MyClass {
typename T::SubType* ptr;
...
};
如果不加typename
茶袒,SubType
會被認(rèn)為是T
的一個靜態(tài)成員,而不會被認(rèn)為是一個內(nèi)部類型凉馆。
成員模板:
如果類的成員函數(shù)也是獨(dú)立的模板函數(shù)薪寓,則稱之為成員模板。例如澜共,給Stack<>
類增加一個賦值操作符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ù)也是一個模板的時候,情況就變得復(fù)雜了嗦董。例如母谎,Stack<T, CONT>
中,模板參數(shù)CONT
就是一個模板京革,我們需要傳入std::vector<T>
作為實(shí)參奇唤。但是這樣做無法約束vector
的模板參數(shù)T
與Stack
的第一個模板參數(shù)T
一致,有可能出錯存崖。這種情況下使用模板的模板參數(shù)更合適冻记。
template <typename T, template <typename ELEM> class CONT = std::deque>
class Stack {
private:
CONT<T> elems;
...
};
使用時不必傳入容器類的模板參數(shù),它會自動根據(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ù)類型通常具有一個不確定(undefined)值隅居。因此建議采用如下寫法:
template <typename T>
void foo()
{
T x; //不建議這樣寫,如果T是基本數(shù)據(jù)類型葛虐,那么x本身是一個不確定的值
T x = T(); //建議這樣寫胎源,如果T是基本數(shù)據(jù)類型,那么x是0或者false
}
五屿脐、模板代碼的組織結(jié)構(gòu)
我們通常把聲明寫在.h文件中涕蚤,把定義寫在.cpp文件中。然而這種慣例被模板打破了的诵。
如果把函數(shù)模板的聲明和定義分別寫在兩個文件中万栅,鏈接器將會報(bào)錯,提示找不到函數(shù)的定義西疤。這是因?yàn)榉沉#瘮?shù)模板還沒有實(shí)例化,也就是說代赁,函數(shù)模板的定義所在的文件并沒有被編譯扰她,因?yàn)榫幾g器不知道應(yīng)該使用哪個模板參數(shù)來實(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)委員會廢除。
找錯誤答案
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
快集,因此需要一個從double
到int
的轉(zhuǎn)換贡羔,這次轉(zhuǎn)換將會創(chuàng)建一個臨時int
型變量作為返回值。而由于返回類型為引用个初,導(dǎo)致該函數(shù)返回后將持有一個臨時局部變量的引用乖寒,一旦臨時變量被釋放,繼續(xù)使用這個引用將得到意想不到的結(jié)果院溺,甚至引起程序崩潰楣嘁。
解決方案是返回值不使用引用。這個問題很容易出現(xiàn)珍逸,與此類似的還有返回局部變量的指針逐虚,因此編程時需要多加注意。
關(guān)注作者或文集《C++ Templates》谆膳,第一時間獲取最新發(fā)布文章叭爱。