第十六章模板和泛型編程
在C++中模板是泛型編程的基礎(chǔ)。(In C++, templates are the foundation for generic programming.)
16.1 模板定義Template Definitions
16.1.1 定義函數(shù)模板 Defining a Function Template
函數(shù)模板是和類型無關(guān)的函數(shù)定義,它被當(dāng)作是公式用來生成特定類型版本的函數(shù)。(A function template is a type-independent function that is used as a formula for generating a type-specific version of the function.)
// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T> //模板參數(shù)列表template parameter list
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
|
模板參數(shù)列表不能為空。(The template parameter list cannot be empty.)這很好理解哈,如果沒有參數(shù),還要模板干嘛。
類型形參
上面這個(gè)例子,模板形參是類型形參(type parameter)。類型形參代表的是類型,定義的跟在關(guān)鍵字class或者typename之后。
使用函數(shù)模板
編譯器一旦確定了模板的實(shí)參,就會(huì)實(shí)例化模板函數(shù)。(Once the compiler determines the actual template argument(s), it instantiates an instance of the function template for us.)編譯器生成并編譯用實(shí)參取代對應(yīng)的模板形參的函數(shù)版本。編譯器承擔(dān)了為每種類型重寫函數(shù)的枯燥的任務(wù)。(it generates and compiles a version of the function using those arguments in place of the corresponding template parameters. The compiler takes on the tedium of (re)writing the function for each type we use.)
要明確的是編譯器是在編譯期完成這些任務(wù)的。
int main ()
{
// T is int;
// compiler instantiates int compare(const int&, const int&)
cout << compare(1, 0) << endl;
// T is string;
// compiler instantiates int compare(const string&, const string&)
string s1 = "hi", s2 = "world";
cout << compare(s1, s2) << endl;
return 0;
}
|
16.1.2 定義類模板Defining a Class Template
template <class Type> class Queue {
public:
Queue (); // default constructor
Type &front (); // return element from head of Queue
const Type &front () const;
void push (const Type &); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ...
};
//使用模板
Queue<int> qi; // Queue that holds ints
Queue< vector<double> > qc; // Queue that holds vectors of doubles
Queue<string> qs; // Queue that holds strings
|
16.1.3. 模板形參 Template Parameters
Glorp就是模板的形參。
// equivalent template definition
template <class Glorp>
int compare(const Glorp &v1, const Glorp &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
|
模板形參作用域
模板形參的作用域是從模板形參從聲明之后,直到模板聲明或者定義結(jié)束。(The name of a template parameter can be used after it has been declared as a template parameter and until the end of the template declaration or definition.)
注意:模板形參會(huì)屏蔽全局的同名的對象,函數(shù)或者是類型。
typedef double T;
template <class T> T calc(const T &a, const T &b)
{
// tmp has the type of the template parameter T
// not that of the global typedef
T tmp = a; //T對應(yīng)的是模板類型形參,而不是double
// ...
return tmp;
}
|
模板聲明Template Declarations
每一個(gè)模板類型形參前面都必須以關(guān)鍵字class或者typename;每個(gè)非類型形參前面必須是類型的名稱。(Each template type parameter must be preceded either by the keyword class or typename; each nontype parameter must be preceded by a type name.)
// declares compare but does not define it
template <class T> int compare(const T&, const T&) ;
|
16.1.4. 模板類型形參 Template Type Parameters
typename和class的區(qū)別
二者其實(shí)是沒有區(qū)別的,只是為了更加直觀,才區(qū)分是typename環(huán)視class。當(dāng)然,混用也是可以的:
template <typename T, class U> calc (const T&, const U&);
|
在模板定義里定義類型
關(guān)鍵字typename表示size_type是綁定到Parm上的類型,而不是數(shù)據(jù)成員。
template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
typename Parm::size_type * p; // ok: declares p to be a pointer
}
|
16.1.5. 非類型模板形參 Nontype Template Parameters
模板非類型形參是常量。
// initialize elements of an array to zero
template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
//calling
int x[42];
double y[10];
array_init(x); // instantiates array_init(int(&)[42]
array_init(y); // instantiates array_init(double(&)[10]
|
通過引用傳遞數(shù)組
需要參看一下Section7.2.4中“通過引用傳遞數(shù)組(Passing an Array by Reference)”。
這是從Section7.2.4抄過來的一個(gè)例子:(嘎嘎,大師就是有先見之明,Section7.2.4就是直接指到這部分的。)
// ok: parameter is a reference to an array; size of array is fixed
//arr是一個(gè)int類型,長度是10的數(shù)組的引用
void printValues( int (&arr)[10] ) { /* ... */ }
int main()
{
int i = 0, j[2] = {0, 1};
int k[10] = {0,1,2,3,4,5,6,7,8,9};
int (&int_ref)[10] = k; //這樣就得到了一個(gè)數(shù)組的引用
printValues(&i); // error: argument is not an array of 10 ints
printValues(j); // error: argument is not an array of 10 ints
printValues(k); // ok: argument is an array of 10 ints
return 0;
}
|
@arr的圓括號是不能少的,因?yàn)橄聵?biāo)操作符具有更高的優(yōu)先級。
f(int &arr[10]) // error: arr is an array of references
f(int (&arr)[10]) // ok: arr is a reference to an array of 10 ints
|
再多說點(diǎn),關(guān)于類型定義
想要定義一個(gè)數(shù)組引用類型,方法如下
typedef 類型名 (&數(shù)組引用類型名)[N];
實(shí)例
typedef int (&Array_Ref)[10];
Array_Ref就是一個(gè)數(shù)組的引用類型了。
16.1.6. 編寫泛型程序 Writing Generic Programs
當(dāng)模板定義完成后,模板實(shí)際上認(rèn)為類型都是有效的。但是實(shí)際使用模板的用戶,所使用的類型有可能就是無效的。
生成的程序是否合法取決于函數(shù)中用到的操作以及類型能夠支持的操作。(Whether the generated program is legal depends on the operations used in the function and the operations supported by the type or types used.)說出來很拗口,但是稍微看一下大師給的例子,就明鏡一樣的啦。
if (v1 < v2) return -1; // < on two objects of type T
if (v2 < v1) return 1; // < on two objects of type T
return 0; // return int; not dependent on T
|
如果對象類型不支持’<’翹了L就比如說Sale_item。
編寫和類型無關(guān)的代碼
原則:
編寫模板代碼時(shí),對類型的需求盡可能少。(When writing template code, it is useful to keep the number of requirements placed on the argument types as small as possible.)
開始的例子compare就能夠說明上面的原則。
l 模板的形參是const引用,就避免了copy,這樣不允許copy的類類型也可以使用這個(gè)模板了。
l 模板函數(shù)體只使用<操作符。減少了支持的操作符
// expected comparison
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;
|
// expected comparison
if (v1 < v2) return -1;
if (v2 < v1) return 1; // equivalent to v1 > v2
return 0;
|
后者對類類型的需求要少些,不必支持’<’和’>’兩種操作符。
模板鏈接時(shí)的編譯錯(cuò)誤
當(dāng)編譯模板時(shí),有三個(gè)階段可以標(biāo)識錯(cuò)誤:
1. 編譯模板本身時(shí)
2. 當(dāng)編譯器看到一個(gè)模板使用時(shí)
3. 在模板實(shí)例化時(shí)
16.2 Instantiation
模板實(shí)例化定義
模板是一個(gè)藍(lán)本,模板本身不是類或者函數(shù)。編譯器使用模板生成特定類型版本的類或者函數(shù)。這個(gè)特定類型的模板實(shí)例過程叫做實(shí)例化。(A template is a blueprint; it is not itself a class or a function. The compiler uses the template to generate type-specific versions of the specified class or function. The process of generatng a type-specific instance of a template is known as instantiation.)
類實(shí)例化:
這是模板的定義:
template <class Type> class Queue {
public:
Queue (); // default constructor
Type &front (); // return element from head of Queue
const Type &front () const;
void push (const Type &); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ...
};
|
Queue<int> qi;這句話對應(yīng)的就是實(shí)例化類Queue<int>。
編譯器通過重寫模板Queue生成Queue<int>類。就是說每一個(gè)模板類實(shí)例化,實(shí)際上都會(huì)產(chǎn)生新的類,并且由模板類生成的類之間是沒有任何關(guān)系的。Queue<int>和 Queue<string> 是沒有任何關(guān)系的,雖然它們都是由同一個(gè)類模板Queue生成的。
每個(gè)類模板實(shí)例化生成一個(gè)獨(dú)立的類類型。(Each instantiation of a class template constitutes an independent class type.)
// simulated version of Queue instantiated for type int
template <class Type> class Queue {
public:
Queue(); // this bound to Queue<int>*
int &front(); // return type bound to int
const int &front() const; // return type bound to int
void push(const int &); // parameter type bound to int
void pop(); // type invariant code
bool empty() const; // type invariant code
private:
// ...
};
|
函數(shù)模板實(shí)例化
函數(shù)模板實(shí)例化會(huì)生成不同類型版本的函數(shù)。
16.2.1模板實(shí)參推斷 Template Argument Deduction
從函數(shù)實(shí)參的類型確定模板實(shí)參的類型和值的過程叫做模板實(shí)參推斷。(The process of determining the types and values of the template arguments from the type of the function arguments is called template argument deduction.)
多種類型形參的實(shí)參必須完全匹配
初聽起來,像天書,其實(shí)說的是
模板類型參數(shù)有可能用作是函數(shù)的一個(gè)或者多個(gè)形參。這種情況下,模板類型推斷必須對每一對應(yīng)的函數(shù)實(shí)參生成同樣的模板實(shí)參類型。(A template type parameter may be used as the type of more than one function parameter. In such cases, template type deduction must generate the same template argument type for each corresponding function argument. )
還是拗口,看例子就一目了然。
//模板定義:
template <typename T>
int compare(const T& v1, const T& v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int main()
{
short si;
//實(shí)例化,翹了L
// error: cannot instantiate compare(short, int)
// must be: compare(short, short) or
// compare(int, int)
compare(si, 1024);
return 0;
}
|
類型形參的實(shí)參的有限轉(zhuǎn)換
編譯器只能支持兩種類型的轉(zhuǎn)換,而不需實(shí)例化。
· const轉(zhuǎn)換。
如果函數(shù)的形參是指向const對象的引用或指針,這個(gè)函數(shù)可以被非const對象的引用或指針調(diào)用。(A function that takes a reference or pointer to a const can be called with a reference or pointer to nonconst object,)
如果函數(shù)接受非引用類型,那么在形參類型或?qū)崊⑸隙紩?huì)忽略const。(If the function takes a nonreference type, then const is ignored on either the parameter type or the argument.)
這就是說我們是否傳遞一個(gè)const或非const對象給一個(gè)接受非引用類型的函數(shù),都會(huì)使用同一個(gè)實(shí)例。(That is, the same instantiation will be used whether we pass a const or nonconst object to a function defined to take a nonreference type.)
· 數(shù)組或函數(shù)到指針的轉(zhuǎn)換。
如果模板形參不是引用類型,那么數(shù)組或函數(shù)類型的實(shí)參可以使用正常的指針轉(zhuǎn)換。(If the template parameter is not a reference type, then the normal pointer conversion will be applied to arguments of array or function type. )
數(shù)組實(shí)參可以看作是指向第一個(gè)元素的指針,函數(shù)實(shí)參可以看作是指向函數(shù)類型的指針。(An array argument will be treated as a pointer to its first element, and a function argument will be treated as a pointer to the function's type.
正常的轉(zhuǎn)換依然可以用在非模板類型的實(shí)參上。
這句話的意思是,上面所討論的轉(zhuǎn)換模式僅限于模板類型形參。
模板實(shí)參和函數(shù)指針
復(fù)習(xí)函數(shù)指針:
//pf是一個(gè)函數(shù)指針,函數(shù)的返回值是bool,形參是兩個(gè)string類型的引用
bool (*pf)(const string &, const string &);
//函數(shù)定義
bool lengthCompare(const string &, const string &);
//函數(shù)指針賦值
pf= lengthCompare
|
通過模板實(shí)參定義函數(shù)指針:
template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare (const int&, const int&)
int (*pf1) (const int&, const int&) = compare;
|
一旦函數(shù)模板實(shí)例的地址可以得到,上下文必須為每個(gè)模板形參確定唯一的類型或者值。(When the address of a function-template instantiation is taken, the context must be such that it allows a unique type or value to be determined for each template parameter.)
16.2.2.函數(shù)模板的顯式實(shí)參 Function-Template Explicit Arguments
為什么要使用顯式實(shí)參?
這是因?yàn)樵谀承┨厥馇闆r下,是不可能推斷出模板實(shí)參的類型的。問題都是出在函數(shù)的返回值類型和形參列表中的類型不同。(This problem arises most often when a function return type must be a type that differs from any used in the parameter list.)這種情況下,就需要顯式指定模板形參的類型或者值。
指定顯式模板實(shí)參
這種方法就是由模板用戶來決定返回值類型。
// T or U as the returntype?模板定義
template <class T, class U> ??? sum(T, U);
// ok: now either T or U works as return type
int i; short s;
sum(static_cast<int>(s), i); // ok: instantiates int sum(int, int)
|
在使用模板前預(yù)判斷。這個(gè)判斷要由模板用戶來完成。
返回值類型使用類型形參
就是說為函數(shù)的返回值也指定類型形參。
模板定義:
// T1 cannot be deduced: it doesn't appear in the function parameter list
//在模板實(shí)參推斷時(shí),T1是不起作用的。
template <class T1, class T2, class T3>
T1 sum(T2, T3);
|
模板實(shí)例化:
// ok T1 explicitly specified; T2 and T3 inferred from argument types
long val3 = sum<long>(i, lng); // ok: calls long sum(int, long)
|
顯式模板實(shí)參是按從左到由的順序進(jìn)行匹配的。
顯式實(shí)參和函數(shù)模板指針
template <typename T> int compare(const T&, const T&);
// overloaded versions of func; each take a different function pointer type
void func(int(*) (const string&, const string&));
void func(int(*) (const int&, const int&));
func(compare<int>); // ok: explicitly specify which version of compare
|
應(yīng)用場景:
func是overload的,它的形參可以是不同的函數(shù)指針。因此可以使用顯式實(shí)參來指定函數(shù)指針,也就是說指定模板的實(shí)例。
16.3 模板編譯模式Template Compilation Models
只有當(dāng)編譯器發(fā)現(xiàn)模板使用的時(shí)候,才生成代碼。
一般程序的編譯過程
先說說編譯器對一般的函數(shù)調(diào)用的編譯方法。單獨(dú)調(diào)用一個(gè)函數(shù)時(shí),編譯器只需要看到這個(gè)函數(shù)的聲明;類似,當(dāng)定義一個(gè)類類型的對象時(shí),類定義必須是有效的,但是成員函數(shù)的定義不必出現(xiàn)。這樣我們就可以把函數(shù)聲明以及類定義放在頭文件里,而函數(shù)定義以及類成員函數(shù)的定義都放在源文件里。(Ordinarily, when we call a function, the compiler needs to see only a declaration for the function. Similarly, when we define an object of class type, the class definition must be available, but the definitions of the member functions need not be present. As a result, we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files.)
包含編譯模型Inclusion Compilation Model
頭文件utlities.h
#ifndef UTLITIES_H // header gaurd (Section 2.9.2, p. 69)
#define UTLITIES_H
template <class T> int compare(const T&, const T&);
// other declarations
#include "utilities.cc" // get the definitions for compare etc.
#endif
|
utilities.cc
template <class T> int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
|
分別編譯模型Separate Compilation Model
模板函數(shù):
在模板函數(shù)定義的源文件中把模板定義為export
// the template definition goes in a separately-compiled source file
export template <typename Type>
Type sum(Type t1, Type t2) /* ..模板定義.*/
|
但是在頭文件的聲明中就無需再定義為export。
模板類:
類定義在頭文件里:
// class template header goes in shared header file
template <class Type> class Queue { ... }; /* ..模板類定義.*/
|
源文件Queue.cc定義成員函數(shù):
// 在源文件中把類聲明為exported
export template <class Type> class Queue;
#include "Queue.h"
// Queue member definitions
|
Export關(guān)鍵字表示給出的定義可能需要在其它的文件里生成實(shí)例。(The export keyword indicates that a given definition might be needed to generate instantiations in other files.)
16.4 Class Template Members
這部分主要是講述如何定義類模板的成員。
16.4.1 類模板成員函數(shù)(Class-Template Member Functions)
類模板成員函數(shù)定義的固定格式:
- 關(guān)鍵字template開頭,后面跟著模板形參。
- 說明類成員
- 類名字必須包括模板形參
對于Queue,它的成員函數(shù)在類外定義,就必須是一下的格式:
template <class T> ret-type Queue<T>::member-name
|
具體到destroy函數(shù):
template <class Type> void Queue<Type>::destroy()
{
while (!empty())
pop();
}
|
類模板成員函數(shù)的實(shí)例化
首先類模板的成員函數(shù)它們自己本身也是函數(shù)模板。
但是和函數(shù)模板不同的地方在模板實(shí)參推斷上。細(xì)細(xì)說明:
- 函數(shù)模板的實(shí)參推斷是在函數(shù)調(diào)用時(shí)發(fā)生的。實(shí)參推斷(template-argument deduction),然后就是函數(shù)模板實(shí)例化。
- 類模板成員函數(shù)呢。當(dāng)類模板成員函數(shù)實(shí)例化時(shí)編譯器不執(zhí)行實(shí)參推斷。類模板成員函數(shù)的模板形參取決于調(diào)用這個(gè)函數(shù)的對象的類型。(Instead, the template parameters of a class template member function are determined by the type of the object on which the call is made.)舉例說,就是,當(dāng)我們調(diào)用Queue<int>類型對象的push成員函數(shù)時(shí),push函數(shù)實(shí)例化為:
void Queue<int>::push(const int &val)
|
這樣,因?yàn)椴淮嬖趯?shí)參推斷(template-argument deduction),如果函數(shù)形參是采用模板形參來定義,允許函數(shù)實(shí)參執(zhí)行普通類型轉(zhuǎn)換:(Normal conversions are allowed on arguments to function parameters that were defined using the template parameter:)
Queue<int> qi; // instantiates class Queue<int>
short s = 42;
int i = 42;
// ok: s converted to int and passed to push
qi.push(s); // instantiates Queue<int>::push(const int&)
qi.push(i); // uses Queue<int>::push(const int&)
f(s); // instantiates f(const short&)
f(i); // instantiates f(const int&)
|
類和成員啥時(shí)實(shí)例化
只有程序用到的類模板的成員函數(shù)才實(shí)例化。
這是P309的例子,這個(gè)序列容器初始化函數(shù)只有一個(gè)size形參。調(diào)用容器的這個(gè)構(gòu)造函數(shù)來初始化一個(gè)對象,實(shí)際上要用到單元類型的缺省構(gòu)造函數(shù)。
const list<int>::size_type list_size=64;
list<int> ilist(list_size);
|
當(dāng)我們定義一個(gè)模板類型的對象時(shí),類模板就會(huì)實(shí)例化。同時(shí)還會(huì)實(shí)例化任何用來初始化對象的構(gòu)造函數(shù),以及構(gòu)造函數(shù)調(diào)用的任何成員。(When we define an object of a template type, that definition causes the class template to be instantiated. Defining an object also instantiates whichever constructor was used to initialize the object, along with any members called by that constructor:)
// instantiates Queue<int> class and Queue<int>::Queue()
Queue<string> qs;
qs.push("hello"); // instantiates Queue<int>::push
|
16.4.2. 非類型形參的模板實(shí)參(Template Arguments for Nontype Parameters)
非類型模板實(shí)參必須是編譯期的常量表達(dá)式。(Nontype template arguments must be compile-time constant expressions.)
16.4.3. 類模板中的友元聲明(Friend Declarations in Class Templates)
大體可以分成三類:
普通友元:
template <class Type> class Bar {
// grants access to ordinary, nontemplate class and function
friend class FooBar;
friend void fcn();
// ...
};
|
一般模板友元關(guān)系:(General Template Friendship)
template <class Type> class Bar {
// grants access to Foo1 or templ_fcn1 parameterized by any type
template <class T> friend class Foo1;
template <class T> friend void templ_fcn1(const T&);
// ...
};
|
建立的是一種一對多的映射關(guān)系。對于每一個(gè)Bar實(shí)例,所有的Fool或temp_fcn1實(shí)例都是它的友元。
特定模板友元
區(qū)別于一般模板友元,類可以只允許特定的實(shí)例訪問。下面的這個(gè)例子,只允許char*的Foo2和templ_fcn2作為Bar的友元。
template <class T> class Foo2;
template <class T> void templ_fcn2(const T&);
template <class Type> class Bar {
// grants access to a single specific instance parameterized by char*
friend class Foo2<char*>;
friend void templ_fcn2<char*>(char* const &);
// ...
};
|
更加通用的寫法是只允許和Bar類型一致的類或者函數(shù)模板實(shí)例作為友元:
template <class T> class Foo3;
template <class T> void templ_fcn3(const T&);
template <class Type> class Bar {
// each instantiation of Bar grants access to the
// version of Foo3 or templ_fcn3 instantiated with the same type
friend class Foo3<Type>;
friend void templ_fcn3<Type>(const Type&);
// ...
};
|
聲明依賴性(Declaration Dependencies)
當(dāng)模板的所有實(shí)例都可以訪問時(shí),也就是說模板是友元時(shí),在作用域里不必聲明這個(gè)類模板或者函數(shù)模板。編譯器把友元聲明當(dāng)作是類或函數(shù)聲明。(When we grant access to all instances of a given template, there need not be a declaration for that class or function template in scope. Essentially, the compiler treats the friend declaration as a declaration of the class or function as well.)
例如:template <class S> friend class D;
如果限制只有特定的實(shí)例才能作為友元,那么類模板或者函數(shù)模板就必須先聲明,才能用作友元聲明。(When we want to restrict friendship to a specific instantiation, then the class or function must have been declared before it can be used in a friend declaration:)
例如:friend class A<T>; 這是正確的聲明
friend class E<T>;錯(cuò)誤的聲明,因?yàn)轭惸0?/span>E沒有在前面聲明。
Template <class T> class A; //這是一個(gè)模板聲明
template <class T> class B {
public:
friend class A<T>; // ok: A is known to be a template
friend class C; // ok: C must be an ordinary, nontemplate class
template <class S> friend class D; // ok: D is a template
friend class E<T>; // error: E wasn't declared as a template
friend class F<int>; // error: F wasn't declared as a template
};
|
16.4.4. Queue 和 QueueItem 的友元聲明 (Queue and QueueItem Friend Declarations)
QueueItem的友元應(yīng)該是和它同類型的Queue。
template <class Type> class Queue;
template <class Type> class QueueItem {
friend class Queue<Type>;
// ...
};
|
輸出操作符重載’<<’
重新溫習(xí)一下14.2輸入和輸出操作符,關(guān)于為什么輸出操作符必須是非成員函數(shù)。
重載輸出操作符一般的簡單定義如下:
// general skeleton of the overloaded output operator
ostream&
operator <<(ostream& os, const ClassType &object)
{
// any special logic to prepare object
// actual output of members
os << // ...
// return ostream object
return os;
}
|
然后再拉回到模板類Queue:
template <class Type>
ostream& operator<<(ostream &os, const Queue<Type> &q)
{
os << "< ";
QueueItem<Type> *p;
for (p = q.head; p; p = p->next)
os << p->item << " ";
os <<">";
return os;
}
|
輸出操作符重載,需要直接訪問Queue的head和QueueItem的next方法和item數(shù)據(jù)成員,因此輸出操作符重載就必須是Queue和QueueItem的友元。
// 函數(shù)模板聲明必須先于友元聲明
template <class T> std::ostream& operator<<(std::ostream&, const Queue<T>&);
template <class Type> class QueueItem {
friend class Queue<Type>;
// needs access to item and next
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
// ...
};
template <class Type> class Queue {
// needs access to head
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
};
|
16.4.5. 成員模板 (Member Templates)
成員模板
類的成員是類模板或者是函數(shù)模板,這樣的成員就是成員模板。成員模板不能是虛函數(shù)。(Any class (template or otherwise) may have a member that is itself a class or function template. Such members are referred to as member templates. Member templates may not be virtual.)
定義
這部分是以Queue中拷貝構(gòu)造函數(shù)(copy constructor)和賦值操作(assignment operator)來舉例說明的。這兩個(gè)函數(shù)都是模板函數(shù)。
template <class Type> class Queue {
public:
// construct a Queue from a pair of iterators on some sequence
template <class It> Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// rest of Queue class as before
private:
// version of copy to be used by assign to copy elements from iterator range
template <class Iter> void copy_elems(Iter, Iter);
};
|
在類定義以外定義成員模板
當(dāng)成員模板是類模板的成員時(shí),成員模板的定義必須包含類模板的形參以及成員函數(shù)自身的模板形參。類模板的形參在前,緊跟著是成員函數(shù)自己的模板形參列表。(When a member template is a member of a class template, then its definition must include the class-template parameters as well as its own template parameters. The class-template parameter list comes first, followed by the member's own template parameter list.)
// template <class Type>類模板形參
// template <class It>成員模板形參
template <class Type> template <class It>
void Queue<Type>::copy_elems(It beg, It end)
{
while (beg != end) {
push(*beg);
++beg;
}
}
|
成員模板和實(shí)例化
只有當(dāng)成員模板被使用時(shí),它才被實(shí)例化。
成員模板包含有兩類模板形參:
1. 由類定義的模板形參。這部分是固定的,由調(diào)用函數(shù)的對象決定。(The class template parameters are fixed by the type of the object through which the function is called. )
2. 由成員模板自己定義的模板形參。由模板實(shí)參推斷確定。(These parameters are resolved through normal template argument deduction )
16.4.7. 類模板的 static 成員 static Members of Class Templates
類模板靜態(tài)成員聲明
template <class T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};
|
類模板是可以定義靜態(tài)成員的。但是和一般的類不同,每一個(gè)類模板的實(shí)例都有自己的靜態(tài)成員。
使用類模板的靜態(tài)成員
類模板的靜態(tài)成員可以通過類類型的對象來訪問,或者使用作用域操作符直接訪問。當(dāng)然,通過類來使用靜態(tài)成員,必須引用實(shí)際的實(shí)例。(we can access a static member of a class template through an object of the class type or by using the scope operator to access the member directly. Of course, when we attempt to use the static member through the class, we must refer to an actual instantiation:)
Foo<int> fi, fi2; // instantiates Foo<int> class
size_t ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // ok: uses Foo<int>::count
ct = fi2.count(); // ok: uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?
|
定義靜態(tài)成員
以關(guān)鍵字template開頭,緊跟著類模板形參列表和類名。(It begins with the keyword template followed by the class template parameter list and the class name.)
template <class T> size_t Foo<T>::ctr = 0; // define and initialize ctr
|
16.5泛型句柄類(A Generic Handle Class)
其實(shí)大師前面講過兩種Handler類:
1. Sales_item:指針型句柄(Pointerlike Handle)(參看:15.8.1. 指針型句柄)
class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): p(0), use(new std::size_t(1)) { }
// attaches a handle to a copy of the Item_base object
Sales_item(const Item_base&);
// copy control members to manage the use count and pointers
Sales_item(const Sales_item &i):
p(i.p), use(i.use) { ++*use; }
~Sales_item() { decr_use(); }
Sales_item& operator=(const Sales_item&);
// member access operators
const Item_base *operator->() const { if (p) return p;
else throw std::logic_error("unbound Sales_item"); }
const Item_base &operator*() const { if (p) return *p;
else throw std::logic_error("unbound Sales_item"); }
private:
Item_base *p; // pointer to shared item
std::size_t *use; // pointer to shared use count
// called by both destructor and assignment operator to free pointers
void decr_use()
{ if (--*use == 0) { delete p; delete use; } }
};
|
2. Query:值型句柄(Valuelike Handle)
// handle class to manage the Query_base inheritance hierarchy
class Query {
// these operators need access to the Query_base* constructor
friend Query operator~(const Query &);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const std::string&); // builds a new WordQuery
// copy control to manage pointers and use counting
Query(const Query &c): q(c.q), use(c.use) { ++*use; }
~Query() { decr_use(); }
Query& operator=(const Query&);
// interface functions: will call corresponding Query_base operations
std::set<TextQuery::line_no>
eval(const TextQuery &t) const { return q->eval(t); }
std::ostream &display(std::ostream &os) const
{ return q->display(os); }
private:
Query(Query_base *query): q(query),
use(new std::size_t(1)) { }
Query_base *q;
std::size_t *use;
void decr_use()
{ if (--*use == 0) { delete q; delete use; } }
};
|
二者相比較:
相同點(diǎn):都保存一個(gè)類型對象的指針;并且都包含一個(gè)使用計(jì)數(shù)的指針。
不同點(diǎn):
· 值型句柄只為保存的對象提供接口。值型句柄不定義解引用和箭頭操作符的。值型句柄包含的的對象的類類型是沒有public成員的。
· 值型句柄包含的的對象的類類型是沒有public成員的。對這個(gè)類的訪問都是通過值型句柄來完成的。這可以理解為值型句柄為保存的對象提供接口。
問題是難道我們要寫N多的各種各樣的Handler類嗎?No,No,No,泛型編程。我們可以定義一個(gè)類模板來管理指針并完成使用計(jì)數(shù)的功能。(This kind of problem is well suited to generic programming: We could define a class template to manage a pointer and do the use-counting.)
16.5.1. 定義句柄類(Defining the Handle Class)
賦值操作符的定義可以參看16.4.5 成員模板(Member Templates)
16.5.2. 使用句柄(Using the Handle)
有必要重新復(fù)習(xí)一下箭頭操作符重載P525
point->action();
由于優(yōu)先級規(guī)則,它實(shí)際等價(jià)于編寫:
(point->action)();
編譯器執(zhí)行這段代碼:
1. 如果point是對象,并且這個(gè)對象所屬的類定義了operator->。那么就等價(jià)于:
point.operator->()->action
L之所以繞不出來,就在這里,一定以要記住這個(gè)等價(jià)表示,它包含了兩個(gè)’->’。
2. 如果point是指向?qū)ο蟮闹羔槪敲淳幾g器調(diào)用對象的action方法
使用句柄
調(diào)用 net_price 函數(shù)的語句值得仔細(xì)分析一下:
sum += (*iter)->net_price(items.count(*iter));
|
唉,你不得不服啊,大師,選的這的這語句。
· (*iter) 返回Sales_item對象。(書中說返回的是h,但是我覺得是Sales_item對象)
· 因此,(*iter)-> 使用句柄類Sales_item的重載箭頭操作符。
等價(jià)于:
(*iter). operator->()->net_price(items.count(*iter));
· 編譯器計(jì)算 h.operator->(),獲得該 Handle 對象保存的 Item_base 指針。
16.6 模板特化(Template Specializations)
lzn曾經(jīng)和我說起,這是C++的一個(gè)重要特性。也是因?yàn)樗沟?/span>C++比Java和C#更加的靈活。今天終于看到這部分了,excitedJ
模板特化的背景是有時(shí)模板類或者模板函數(shù)并不適合所有的類型(type)。但是對于模板用戶來說這種特化又是透明的。
16.6.1. 函數(shù)模板的特化 Specializing a Function Template
對比在一起看或許更明了。前面的是模板函數(shù)compare:
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
|
這是特化函數(shù)模板:
關(guān)鍵字template后面跟的是空的尖括號對<>。
然后跟著模板名稱和尖括號對指定的模板形參。
函數(shù)形參表
函數(shù)體
template <>
int compare<const char*>(const char* const &v1, const char* const &v2)
{
return strcmp(v1, v2);
}
|
聲明模板特化
特化也可以簡單的聲明,而不被定義,其實(shí)聲明和定義很類似,只是缺少了函數(shù)體。
template<> int compare<const char*>(const char* const&, const char* const&);
template<> int compare(const char* const&, const char* const&);
|
這兩個(gè)聲明都是有效的,后者之所以有效,是因?yàn)槟0宓膶?shí)參可以通過函數(shù)的形參列表推斷出來,因此就不需要顯式定義模板的實(shí)參了。(If the template arguments can be inferred from the function parameter list, there is no need to explicitly specify the template arguments)
函數(shù)重載和模板特化
這樣寫是模板特化:
template<> int compare<const char*>(const char* const&, const char* const&);
但是如果這樣寫就是普通的函數(shù)重載:
int compare(const char* const&, const char* const&);
模板特化和函數(shù)是不一樣的。前者不存在類型轉(zhuǎn)換(conversions)。如果是調(diào)用普通的函數(shù),對實(shí)參可以應(yīng)用類型轉(zhuǎn)換;如果特化模板,實(shí)參類型是不能應(yīng)用類型轉(zhuǎn)換的。實(shí)參類型必須完全匹配特化版本函數(shù)形參類型。
重復(fù)定義不總是被檢測到
應(yīng)在一個(gè)頭文件中包含模板特化的聲明,然后使用該特化的每個(gè)源文件包含該頭文件。(declarations for template specializations should be included in a header file. That header should then be included in every source file that uses the specialization.
)
普通的作用域規(guī)則也可以應(yīng)用到特化上
簡單講,就是要先聲明后使用。這句話有兩層含義:
1. 在模板特化定義或者聲明之前要先聲明相應(yīng)的模板。
2. 同樣在特化模板調(diào)用前,要先對特化模板進(jìn)行聲明。
16.6.2. 類模板的特化(Specializing a Class Template)
定義類特化Defining a Class Specialization
還是放在一起看:
template <class Type> class Queue {
public:
void push(const Type &);
…}
|
template<> class Queue<const char*> {
public:
// no copy control: Synthesized versions work for this class
// similarly, no need for explicit default constructor either
void push(const char*);
void pop() {real_queue.pop();}
bool empty() const {return real_queue.empty();}
// Note: return type does not match template parameter type
std::string front() {return real_queue.front();}
const std::string &front() const {return real_queue.front();}
private:
Queue<std::string> real_queue; // forward calls to real_queue
};
|
類特化實(shí)際上只是和原來的模板類具有相同的接口,而實(shí)現(xiàn)上可以完全不同。例如Queue<const char*>,它的數(shù)據(jù)成員只有一個(gè)Queue<std::string> real_queue;。
類模板特化應(yīng)該定義和它特化的模板相同的接口。如果不這樣,當(dāng)模板用戶使用未定義的成員時(shí),就會(huì)感到很吃驚。(A class template specialization ought to define the same interface as the template it specializes. Doing otherwise will surprise users when they attempt to use a member that is not defined.)
類特化定義Class Specialization Definition
在類特化之外定義一個(gè)成員時(shí),前面可以沒有前綴template<>。
void Queue<const char*>::push(const char* val)
{
return real_queue.push(val);
}
|
16.6.3. 特化成員而不特化類(Specializing Members but Not the Class)
應(yīng)用場景:只特化某些成員。
例如對于Queue,其實(shí)只需特化push()和pop()這兩個(gè)成員函數(shù)(member)。這樣可以才應(yīng)特化成員,而不是前面特化類的方法。
特化聲明:它必須以template <>開頭。
// push and pop specialized for const char*
template <> void Queue<const char*>::push(const char* const &);
template <> void Queue<const char*>::pop();
|
16.6.4. 類模板的部分特化(Class-Template Partial Specializations)
應(yīng)用場景:類模板有多個(gè)模板形參,我們也許想要部分模板形參。
類模板特化本身也是模板。(A class template partial specialization is itself a template.)
template <class T1, class T2>
class some_template {
// ...
};
// partial specialization: fixes T2 as int and allows T1 to vary
template <class T1>
class some_template<T1, int> {
// ...
};
|
使用:
some_template<int, string> foo; // uses template
some_template<string, int> bar; // 使用部分特化
|
16.7 重載和函數(shù)模板(Overloading and Function Templates)
函數(shù)模板也能夠重載。正是因?yàn)檫@個(gè)原因,才引出這部分內(nèi)容。
函數(shù)匹配與函數(shù)模板(Function Matching and Function Templates)
確定函數(shù)調(diào)用的步驟:
- 構(gòu)件候選函數(shù)集合。候選函數(shù)包括:
(a)同名的一般函數(shù)。(Any ordinary function with the same name as the called function.)
(b)函數(shù)模板實(shí)例。模板實(shí)參檢測發(fā)現(xiàn)模板實(shí)參和函數(shù)實(shí)參匹配。(Any function-template instantiation for which template argument deduction finds template arguments that match the function arguments used in the call.)
- 決定哪些一般函數(shù)是有效的。(Determine which, if any, of the ordinary functions are viable)
- 通過轉(zhuǎn)換的類型(如果需要轉(zhuǎn)換才能調(diào)用的話),對于有效的函數(shù)進(jìn)行歸類。(Rank the viable functions by the kinds of conversions, if any, required to make the call)
(a) 如果只剩一個(gè)函數(shù),調(diào)用這個(gè)函數(shù)。
(b)如果調(diào)用有二義性,從有效函數(shù)集合中去掉函數(shù)模板實(shí)例。(If the call is ambiguous, remove any function template instances from the set of viable functions.)
- 重新歸類,排除函數(shù)模板實(shí)例。(Rerank the viable functions excluding the function template instantiations.)
· 如果只剩一個(gè)函數(shù),調(diào)用這個(gè)函數(shù)。(If only one function is selected, call this function.)
· 否則,調(diào)用具有二義性。(Otherwise, the call is ambiguous.)
舉例說明函數(shù)模板匹配
函數(shù)和函數(shù)模板定義:
// compares two objects
template <typename T> int compare(const T&, const T&);
// compares elements in two sequences
template <class U, class V> int compare(U, U, V);
// plain functions to handle C-style character strings
int compare(const char*, const char*);
|
調(diào)用:
普通函數(shù)和第一個(gè)函數(shù)模板都是匹配的,但是根據(jù)規(guī)則3(b),普通函數(shù)優(yōu)先。
const char const_arr1[] = "world", const_arr2[] = "hi";
// calls the ordinary function taking const char* parameters
compare(const_arr1, const_arr2);
|
普通函數(shù)的形參是const指針(const char*),第一個(gè)模板函數(shù)的形參是const引用。這兩個(gè)函數(shù)調(diào)用都要做類型轉(zhuǎn)換:從數(shù)組轉(zhuǎn)換到指針。普通函數(shù)優(yōu)先。
// calls the ordinary function taking const char* parameters
char ch_arr1[] = "world", ch_arr2[] = "hi";
compare(ch_arr1, ch_arr2);
|
轉(zhuǎn)換和重載函數(shù)模板
調(diào)用:
char *p1 = ch_arr1, *p2 = ch_arr2;
compare(p1, p2);
|
函數(shù)模板template <typename T> int compare(const T&, const T&); :
char *綁定到T上,這樣函數(shù)的形參是const的,指向char的指針的,引用。無需類型轉(zhuǎn)換。(7.2.2. 引用形參)
函數(shù)調(diào)用int compare(const char*, const char*);:
需要把char * 轉(zhuǎn)換為const char*。要類型轉(zhuǎn)換
結(jié)論:定義函數(shù)模板特化好于使用非模板函數(shù)。(it is almost always better to define a function-template specialization than to use a nontemplate version.)