- 泛型编程鱼面香对象编程一样,都依赖于某种形式的多态性。面向对象编程所依赖的多态性成为运行时多态性,泛型编程所依赖的多态性成为编译时多态性或参数式多态性。
- 模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。
函数模板定义如下:
template <typename T>
int compare(const T &v1, const T &v2){
if(v1 < v2) return -1;
if(v2 < v1) return 1;
}
类模板定义如下:
template<class Type > class Queue{
public:
Queue(){}
Type &front();
const Type &front() const;
void push(const Type &);
void pop();
bool empty();
private:
......
};
- 编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。推导出实际模板实参后,编译器使用实参代替相应的模板形参产生并编译该版本的函数。
- 用作模板形参的名字不能再模板内部重用,例如:
template<class T> T calc(const T &a, const T &b)
{
typedef double T; //error:redeclares template parameter T
T tmp = a;
return tmp;
}
- 同一模板的声明和定义中,模板形参的名字不必相同。
template<class T> T calc(const T&, const T&);
template<class U> U calc(const U&, const U&);
template<class Type> Type calc(const Type &a, const Type &b){}
- 每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明是错误的。
//error:must procede U by either typename or class
template<typename T, U> T calc(const T&, const U&);
- 模板类型形参可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。
- 在模板定义内部指定类型
示例1:
template <class Pram, class U>
Parm fcn(Parm *aray, Uvalue){
Parm::size_type *p;
}
示例二:
template <class Pram, class U>
Parm fcn(Parm *aray, Uvalue){
typename Parm::size_type *p;
}
分析:size_type必定是绑定到Parm的那个类型的成员,但我们不知道size_type是一个类型成员的名字还是一个数据成员的名字,默认情况下,编译器假定这样的名字制定数据成员,而不是类型。如果希望编译器将size_type当做类型,则必须显式告诉编译器,如示例二。
- 非类型模板形参
template<class T, size_t N> void array_init(T (&parm)[N]){
for(size_t i=0;i!=N;++i)
parm[i] = 0;
}
数组名作为参数传递时不转换为指针的例外情况有:
- 数组名用作取地址操作符(&)的操作数
- 数组名用作sizeof操作符的操作数
- 用数组对数组的引用进行初始化时(示例中即为该种情况)
- 实例化
1)类模板实例化
- 类模板不定义类型,只有特定的实例才定义了类型。
- 用模板类定义的类型总是包含模板实参。例如,Queue不是类型,而Queue<int>或Queue<string>才是类型。因此,模板类Queue的实例化示例如下:
Queue<int> qi;
Queue<string> qs;
Queue qs; //错误
2)函数模板实例化
- 使用函数模板时,编译器通常会为我们推断模板实参。
- 模板实参推断
- 多个类型形参的实参必须完全匹配。例如compare(int,int)或compare(shor,shor)
例如:
short si;
compare(si, 1024);//错误
如果compare的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义:
template<typename A, typename B>
int compare(const A &v1, const B &v2){
if(v1<v2) return -1;
if(v2< v1) return 1;
return 0;
}
- 类型形参的实参的受限转换
const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型和实参都忽略const,即无论传递const或非const对象给接受非引用类型的函数,都是用相同的实例化。
数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当做指向函数类型的指针。
示例如下:
template<typename T> T fobj(T, T);
template<typename T> T fref(const T&, const T&); //引用实参
string s1("a value");
const string s2("another value");
fobj(s1, s2); //ignore const
fref(s1, s2); //non const object s1 converted to const reference
int a[10], b[42];
fobj(a, b);
fref(a, b); //error,当参数为引用时,数组不能转换为指针,a和b的类型不匹配,调用出错
- 模板实参推断与函数指针
获取函数模板实例化地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。如果不能从函数指针类型确定模板实参,就会出错。例如:
void func(int(*) (const string &, const string &));
void func(int(*) (const int &, const int &));
func(compare); //error:which instantiation of compare?
- 函数模板的显式实参
- 情景:希望定义名为sum、接受两个不同类型实参的函数模板,希望返回的类型足够大,可以包含按任意次序传递的任意两个类型的两个值的和。怎样才能做到?
在返回类型中使用形参,例如:
template<class T1, class T2, class T3>
T3 alternative_sum(T2, T1);
正确的调用方法为:long val2 = alernative_sum<long, int, long>(i,lng)(其中i为int,lng为long类型)
- 非类型模板实参必须是编译时常量表达式。
- 类模板中的友元声明
- 普通友元
非模板类或非模板函数可以使类模板的友元:
template<class Type> class Bar{
friend class FooBar;
friend void fcn();
......
};
- 一般模板友元关系
template<class Type> class Bar{
template<class T> friend class Foo1;
template<class T> friend void temp1_fcnl(const T&);
......
};
- 特定的模板友元关系
类也可以只授予对特定实例的访问权:
template<class T> class Foo3;
template<class T> void temp1_fcn3(const T&);
template<class type> class Bar{
friend class foo3<Type>;
friend void temp1_fcn3<Type>(const type&);
......
};
Bar<int> bi; //Foo3<int> 和temp1_fcn3<int>是友元
Bar<string> bs; //Foo3<string> 和temp1_fcn3<string>是友元
- 想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数。
template<class T> class A;
template<calss T> class B{
friend class A<T>;
friend class B;
template<class S> friend class D;
friend class E<T>; //error:E没有声明为模板类
friend class F<int>; //error:F没有声明为模板类
};
- 成员模板
- 任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员成为成员模板,成员模板不能为虚。
- 定义成员模板
template<class Type> class Queue{
public:
template<class It> Queue(It beg, It end):head(0), tail(0) {copy_elems(beg, end);}
template<class It> void assign(Iter, Iter);
private:
template<class Iter> void copy_elems(Iter, Iter);
};
- 在类外部定义成员模板
当成员模板是类模板的成员时,它的定义必须包含模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。assign函数定义如下:
template<class T> template<class Iter>
void Queue<T>::assign(Iter beg, Iter end){
....
}
- 成员模板遵循常规访问控制
- 类模板的static成员
Foo类的每个实例化有自己的static成员。每个实例化表示截然不同的类型,所以给定实例化的所有对象都共享一个static成员。
- 实用类模板的static成员
当试图通过类使用static成员时,必须引用实际的实例化.与任意其他成员函数一样,static成员函数只有在程序中使用时才能进行实例化。
Foo<int> fi, fi2;
size_t ct = Foo<int>::count();
ct = fi.count();
ct = fi2.count();
ct = Foo::count(); //error:要指明模板实例化
- 定义static成员
template<class T>
size_t Foo<T>::ctr = 0;
- 模板特化
- 函数模板的特化
模板特化必须总是包含空模板形参说明符,即template<>,而且,还必须包含函数形参表。如果可以从函数形参表推断模板实参,则不必显示指定模板实参。如下:
template<>
int compare<const char*>(const char* const &v1, const char* const &v2){
return strcmp(v1,v2);
}
template<> int compare(cosnt char* const &v1, const char* const &v2);//函数声明
- 类模板的特化
template<> class Queue<const char*1>{
......
};
在类特化外部定义成员时,成员之前不能加template<>标记。例如
void Queue<const char*>::push(const char* val){
reutrn real_queue.push(val);
}
- 特化成员而不特化类、
template<>
void Queue<const char*>::push(const char* const &val){
........
}
- 类模板的部分特化
类模板的部分特化本身也是模板。部分特化的定义看来像模板定义,这种定义以关键字template开头,接着是由尖括号括住的模板形参表。部分特化的模板形参表是对应的类模板定义形参表的子集。部分特化与对应类模板有相同名字,即这里的some_template。
template<class T1, class T2> class come_template{
......
};
template<class T1> class come_template<T1, int>{
......
}
- 重载与函数模板
- 函数匹配与函数模板
函数模板可以重载:可以定义有相同名字但是形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。
当匹配同样好时,非模板版本优先。