一、模板参数列表
模板参数列表是一个逗号分隔的一个或者多个模板参数的列表;
template<typename T, typename U>
如上所示,typename T和typename U为模板参数;
二、模板参数
模板参数分为模板类型参数与非类型模板参数两种;
1)模板类型参数:模板类型参数可以看做类型说明符,可以向类类型说明符和内置类型一样使用;
2)非类型模板参数:非类型模板参数可以是一个整型,或者一个指向对象或者函数类型的指针或引用;非类型模板参数用来表示一个值,
需要通过一个特定的类型名(如int等类型)来指定一个非类型模板参数;非类型模板参数可以通过用户提供的或者编译器推断的值替代,但是用来替代该非
类型模板参数的值必须为常量表达式(即在编译时期就能确定的值),绑定到指针或引用的非类型模板参数的实参需要具有静态生存期;指针参数可以使用
nullptr或者值为0的常量表达式来实例化;
template<typename T, unsigned N> void Move(const T (&source)[N], const T (&dest)[N]) { for (int i = 0; i < N; ++i) dest[i] = source[i]; }
如上所示,该模板使用模板类型参数T和非模板类型参数N来拷贝某种类型的长度为N的数组;
三、模板参数与作用域
在模板参数列表中定义的模板参数名的可用范围同函数参数列表中定义的参数名一样,其可用范围为其声明之后,到模板声明或定义结束之前;
同样的,在模板作用域中不能重用模板参数名;
四、模板的声明
与函数声明相同,模板声明需要包含模板参数,就如函数声明需要包含参数类型与数目;
template <typename T, unsigned N> void Move(const T(&source)[N], const T(&dest)[N]);
五、模板默认实参
如同可以为函数提供默认实参,在类模板或者函数模板中可以提供模板默认实参;
template<typename T = int, unsigned N = 1> class Array { T num_[N]; }; int main() { Array<> test1; Array<double> test2; Array<double, 5> test3; return 0; }
在使用类模板时,无论何时都需要在模板名后提供<>,表示该类从一个模板类实例化而来;所以,当所有的参数为默认模板参数时,仍然需要在模板名后接<>;
六、模板控制实例化
在存在多个对象文件时,如果在多个对象文件中对于同一个模板使用了相同的模板参数时,在这些对象文件中都会生成一个相同的模板实例;
这样会使编译速度变慢,增加不必要的开销,为了减少这样的开销,可以使用显式实例化显式实例化一个模板,得到一个模板实例;
C++提供实例化声明和实例化定义来实现显式实例化,实例化声明表示在其他对象文件中已经存在该模板的一个实例;实例化定义则在该对象文件
中显式实例化该模板的一个实例;
extern template class Array<double, 5>; // 显式实例化声明 template class Array<int, 10>; // 显式实例化定义
在普通情况下,当编译器在编译时检测到程序使用了类模板的成员时才会生成该模板实例中的成员;但是使用显示实例化时,显式实例化类模板会实例化所有的成员,
因为编译器遇到显示实例化声明时就会实例化一个实例,这时无法得知在程序中成员的使用情况;
七、模板编译
1)模板实例化时机:当编译器遇到模板定义时不会实例化模板,当遇到一个模板的使用时,才会生成代码实例化该模板;
2)模板成员定义位置:当实例化一个模板时,编译器需要模板的所有成员信息,模板以及其成员的的定义必须是可见的,所以模板成员的声明与定义需要在同一个文件中;
3)头文件与模板:在模板中存在两种类型名,一种为模板参数列表中出现的模板类型参数,一种为普通的内置类型或者类类型;如同普通类定义一样,类类型需要
在模板定义的位置是可见的;