1.定义模板
模版是C++泛型编程的基础,一个模版就是一个类或函数的蓝图或者说是公式:例如在使用vector这样的泛型类型,或者是find函数这样的泛型类型,我们可以将蓝图转换为特定的类或者是函数,这种转换发生在编译时。当我们调用一个模板时,编译器会使用实参的类型来确定绑定到模版参数T上的类型,之后编译器利用推断出的模版参数来实例化一个特定版本的函数,这个过程被称之为实例化。编译器遇到一个模版的定义时,并不会产生代码,只有当我们实例化出模版的一个特定版本的时候,编译器才会产生代码,因此有很多bug等问题要等用到(实例化)的时候才会发现。
1.1 函数模板
-
类型参数之前必须加上 typename 或者 class 这两个关键字,这两个关键字含义相同,可以互换使用。但有些地方只有 typename 才可以用,比如当我们表示一个容器类型时,只能使用 typename 关键字。
-
模板类型参数包括类型参数和非类型模板参数。类型参数很好理解,不多赘述。下面讲解非类型模板参数。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字 class 或者 typename 来指定非类型参数。
当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。
示例如下:
template <unsigned N, unsigned M> //N和M分别表示数组的长度 int compare(const char (&p1)[N], const char (&p2)[M]) { return strcmp(p1, p2); }
当我们调用这个版本的compare时:compare(“hi”, “mom”); 编译器使用字面常量的大小来代替N和M,从而实例化模板。
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期(参见第12章,第400页)。我们不能用一个普通(非static)局部变量或动态对象作为指针或引用非类型模板参数的实参。指针参数也可以用nullptr或一个值为0的常量表达式来实例化。
-
inline 和 constexpr 这两个说明符的位置和普通的非模板函数一样。
-
与非模板代码不同,函数模板和类模板成员函数的定义通常放在头文件中,即模板的头文件中通常既包括声明也包括定义。
1.2 类模板
-
为了使用类模板,我们必须在模板名后的尖括号中提供额外信息——显示模板实参,它们用来代替模板参数的模板实参列表。
-
默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。这一特性使得即使某种类型不能完全符合模板操作的要求,我们仍能使用该类型实例化类。
-
我们可以在类模版的内部或者外部对类模版的成员函数进行定义,定义在类模版内的成员函数被隐式的声明为inline函数。
-
在类模版自己的作用域中(即类内),我们可以直接使用模版名而不提供实参(不需要这个东西了),而在类外则需要指定模版参数。
-
当一个类模版包含一个非模版友元,则友元被授权可以访问所有的模版实例,如果友元自身是模版,类可以授权给所有友元模版实例,也可以只授予给定实例。如果想要所有实例成为友元,友元声明中必须使用与类模版不同的模版参数。注意这里的前置声明问题。(参见589页)
-
C++11新标准:可以将模版参数类型声明为友元,比如将 Sales_data 类将称为 Bar<Sales_data> 的友元。
虽然友元通常来说应该是一个类或是一个函数,但我们完全可以用一个内置类型来实例化Bar。这种与内置类型的友好关系是允许的,以便我们能用内置类型来实例化Bar这样的类。
-
C++11新标准:我们可以定义一个typedef来引用实例化的类,还可以使用using来声明类型别名。
typedef Blob<string> StrBlob; //引用了实例化的类 template <typename T> using twin = pair<T, T> //起了一个别名 template <typename T> using patrNo = pair<T, unsigned>; partNo<string> books; //books是一个pair<string, unsigned> partNo<Student> kids; //kids是一个pair<Student, unsigned>
1.3 模板参数
- 在类模板中,默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。因此,如果希望使用一个模板类型参数的类型成员,就必须显示告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点。例如如下代码:
template <typename T>
typename T::value_type top(const T& c)
{
if(!c.empty())
return c.back();
else
return typename T::value_type();
}
例如该函数的返回值以及第七行,通过typename指定返回值为T类型的value_type。此处只能使用 typena