函数模板是一个公式,可以来生成针对特定类型的函数版本。以template开始,后面跟一个模板参数列表,里面包含逗号分隔的模板参数。
模板参数是指类中用到的类型或值。使用模板时指定模板实参绑定到模板参数上。
调用函数模板时编译器根据函数实参来推断模板实参,然后用推断出的模板参数实例化一个特定版本的函数。称为模板的实例。
类型参数可以当做类型说明符使用,之前加class或者typename。两个关键字没有区别,typename较新。
模板中还有非类型参数,使用特定类型名定义。可以是整型或者是指向对象或函数类型的指针或(左值)引用。是指针或引用时实参必须有静态生存期。
函数模板中inline或者constexpr放在模板列表之后返回类型之前。
在使用函数模板时,函数参数是const的引用,可以用于不能拷贝的类型。函数体尽量使用与类型无关的代码,减少对于实参类型的要求。
编译器遇到模板定义时不生成代码,在遇到模板的使用时会生成特定类型的实例化代码。为了生成实例化版本的代码,编译器需要掌握函数模板或类模板成员函数的定义。因此模板的头文件通常包括声明和定义。
编译器会在三个阶段报告错误:
类模板用来生成类的蓝图。与函数模板不同,编译器不能为类模板推断模板参数类型。
类模板使用时需要提供显示模板实参来实例化出特定的类。每个类型实例化出来的类互相独立。
类模板的成员函数和普通类的成员函数类似,但是每个实例化的类也对应着不同的实例化的成员函数,因此定义在类模板外的成员函数也需要template 开始加类型参数列表。
在模板内部直接使用类型形参不需要提供实参。
类模板有三种友元关系:
也可以在模板中将类型参数声明为友元。兼容对于内置类型的声明。
可以使用typedef来引用实例化的类,比如typedef Blob
;
新标准允许用类模板定义一个类型别名,如template
,定义一个类型别名时可以固定一个或多个模板参数。
类模板中的static成员对于每个实例化的类来说都有一个对应的实例化的静态成员。
模板参数会隐藏外层作用域声明的相同名字,但是在模板内不能重用模板参数名。因此一个特定模板参数名在列表中只能出现一次。
模板声明必须含有模板参数。
当处理形如 T::size_type*p
的代码时我们需要知道语句表示的是变量定义还是一个乘法,因此需要标示清楚一个名字是否表示一个类型,可以在前面加上关键字typename来实现。
新标准允许为函数模板和类模板提供默认模板实参。
一个类(无论是普通类还是模板类)可以包含本身是模板的成员函数。这种函数叫做成员模板。
特别的,对于类模板的成员模板,在类外定义时既要提供类模板参数列表也要提供成员函数模板参数列表。
显示实例化可以避免重复实例化同一种类。使用extern关键字表明当前模板在其他文件中有实例化,可以避免在当前文件中实例化。每个实例化的版本可以有多个extern声明,但只有一个定义。
实例化声明:extern template class Blob
实例化定义:template int compare(const int&, const int&);
一个类模板的实例化定义会实例化该模板的所有成员,包括内联的成员函数。预处理类模板的普通实例化不同,实例化定义会实例化该类的所有成员。
shared_ptr与unique_ptr的区别,前者共享指针,后者独占指针。
另一个差异是允许用户重载默认删除器的方式。shared_ptr只需要在创建时或reset时传递一个可调用的对象。而unique_ptr必须以显示模板实参的形式提供删除器的类型。
shared_ptr在运行时绑定删除器,需要保存为一个指针或封装指针的类。unique_ptr在编译时就绑定删除器,避免了间接的调用。两种方式一种更灵活,一种效率更高。
传递给函数模板的实参只支持两种类型转换:
其他类型转换如算数转换,都不能应用到函数模板调用。对于模板函数中非模板类型定义的参数也可以应用普通的类型转换。
某些函数模板定义中无法推断出某些类型实参,需要显示指定,如template
中T1的类型无法推断出来,在调用时需要为T1提供显示实参。
显示模板实参按从左至右顺序与对应模板参数匹配,只有尾部参数可以不提供显示实参。
对于已经显示制定了类型的实参允许进行正常的类型转换。
当需要用户确定函数模板的返回类型时,可以在函数模板参数列表尾部指定。比如使用decltype以及标准模板库中的类型转换类。
类型转换类,remove_reference,add_const等等。
当使用一个函数模板初始化一个函数指针或为其赋值,编译器使用指针的类型来推断模板实参。
如果可以确定需要的类型则对函数模板进行实例化,否则的话会编译失败。因此最好在调用时显示指出实例化的版本。
从左值引用推断实参类型:
template
i是int,ci是const int。则:
f(i)中T是int。 f(ci)中T是const int。f(42)调用错误。
template
中:f(i) T是int。f(ci) T是int。f(42) T是int。
对于右值引用,一般只能传递右值或右值引用。但是C++允许在函数模板中将左值传递给右值引用,此时T推断为左值引用类型(第一个例外)。间接创建的引用的引用会导致引用折叠(例外二),折叠规律如下:
导致了两个重要结果:
template
typename remove_reference::type&& move(T&& t) {
return static_cast::type&&>(t);
}
首先形参为模板右值引用,因此可以接收任意类型的实参。然后使用remove_reference模板类来返回右值引用类型。
虽然不能隐式地将一个左值转换为右值引用,但是可以使用static_cast显示地将一个左值转换为一个右值引用。
使用std::move可以保持原始实参再次调用时所有的类型都不变,包括const 左值或是右值引用相关信息。 一般直接使用std::forward。
函数模板可以被另一个函数模板或者非模板函数重载。涉及模板的函数匹配规则如下:
参数T* 比 const T&参数更加特例化,对于同样精准的匹配优先T*。
对于函数模板与非模板函数同名重载时需要注意使用非模板函数时需要进行先声明,否则可能会调用实例化的函数模板。
可变参数模板就是可以 接收可变数目参数的模板函数或模板类。可变数目的参数成为函数包:有模板参数包和函数参数包。
class… 或 typename…表示对于模板参数包的声明。
template
void foo(const T &t, const Args&... rest);
Args是模板参数包,rest表示一个函数参数包,表示0个或多个函数参数。
sizeof…运算符返回一个表示参数包内参数数量的常量表达式。
可变参数函数通常都是递归的,每次解开一个参数包中的参数。
对于参数包除了获取大小操作之外只能够进行扩展。扩展一个包时还要提供每个扩展元素的模式。拓展一个包就是把它分解为其构成的元素,对每个元素应用模式,通过在模式后面加…实现。
直接拓展调用如:print(os,rest...)
加模式之后的调用如:print(os,debug_rep(rest)...)
…符号添加在模式调用的后面表示在模式应用之后进行扩展,否则表示为扩展之后再调用模式,可能会调用失败。
template
inline
void StrVec::emplace_back(Args&&... args) {
chk_n_alloc();
alloc.construct(first_free++,std::forward(args)...);
}
如果调用svec.emplace_back(10,‘c’)会拓展为std::forward
。
可以在模板的基础上对某种类型进行特殊的实例化来满足对特定类型的特殊处理,可以使用template<>前置符号表示对于某个特殊类型的模板的实例化,如:
template<>
int compare(const char* const &p1, const char* const &p2) {
return strcmp(p1,p2);
}
这个函数是template
模板的一个特例化版本,其中T为const char*。
一个特例化版本实质是是一个实例,而非函数名的一个重载版本。因此特例化的模板并不会影响函数匹配。
特例化模板需要在作用域中有原模板的声明。在使用特例化的模板之前也需要声明此特例化的模板。因此一般把同名模板声明都放在一起的一个头文件的前面,然后是它们的特例化版本。
特例化类模板以hash模板定义一个特例化版本为例:
hash模板特例化时需要打开原模板定义所在的命名空间中来特例化。
namespace std {
template<>
struct hash
{
typedef size_t result_type;
typedef Sales_data argument_type;
size_t operator() (const Sales_data& s) const;
};
size_t hash::operator()(const Sales_data& s) const {
return hash()(s.units_sold) ^
hash()(s.revenue);
}
}
由于hash模板类需要调用Sales_data的私有成员,因此需要声明为该类的友元。
也可以将类模板中的模板参数进行部分特例化,提供一部分参数或者一部分特性。部分特例化的类模板依然是一个模板。比如标准库remove_reference类型:
template struct remove_reference {
typedef T type;
}
template struct remove_reference {
typedef T type;
}
template struct remove_reference {
typedef T type;
}
也可以特例化模板的指定成员函数而不是整个模板。