泛型编程
模版是实现泛型编程的一种重要方法。即希望函数或者类可以不受参数类型的限制,进行编程。
它的使用方法如下,即在函数上面加上template,便成为了函数模版。
基本使用以及实例化
template<typename T>
T add(T a, T b) {
return a + b;
}
// 隐式实例化的调用方式
add(3, 4);
// 显式实例化的调用方式
add<int>(3,4);
模版参数实例化分为隐式实例化和显式实例化。
隐式实例化. add(a,b)
:隐式实例化发生在函数调用时,也就是说在运行时通过推导出参数的类型而生成对应的函数实例,因此运行效率偏低。
显式实例化. add<int>(a, b)
:显式实例化会通过显式指定的方式使得编译器在编译阶段(编译时间增加)便会生成对应的实例使得运行时速度加快。
支持类型
函数模版除了可以支持C、C++的基本类型之外,由于特化这一功能的引入,使得函数模版也可以支持用户自定义类型。
非参数类型
模版除了支持类型以外,对非类型的一些行参也是支持的。一般情况下为编译阶段便能确定的整型变量,其他例如浮点等类型还不能够支持。
template<int argNum = 0, size_t N, typename T1, typename... Ts>
void get_args(size_t (%stringBytes)[N], T1 head, Ts... rest) {
get_args(stringBytes[argNum]);
get_args<argNum + 1>(stringBytes, rest...);
}
get_args(stringBytes, args...);
上面以NanoLog日志系统的代码为例,说明了非参数类型的使用。通过argNum来控制数组元素的使用。我们调用get_args(stringBytes, args...)
这个函数,将调用函数模版,并使得初始化argNum为0,进入get_args,进行第一次函数调用,然后根据剩余的可变参数依次进行接下来的函数调用以此来完成相应的函数功能。
上述的 …rest是C++11引入的新特性,用来表示任意数量的函数行参。
template<typename ...Args> //模版参数包 零个或多个类型的列表
void get_args(Args ...args) //函数参数包
sizeof...(Args) : //用于返回不定参数的个数
偏特化
当在函数模版推导的过程中,没有特化满足,或者多于一个特化满足时,编译器都会报错,罢工,这种情况,可以使用enable_if来进行解决
// enable_if
template <bool _Test, class _Ty = void>
struct enable_if {}; // no member "type" when !_Test
template <class _Ty>
struct enable_if<true, _Ty> { // type is _Ty for _Test
using type = _Ty;
};
template<T>
typename std::enable_if<T,T>::type
func(T arg) {
return arg;
}
enable_if 的主要作用就是:当某个条件成立时,enable_if表示指定的类型;当条件不成立时, enable_if表示未定义,但不会报错,编译器在实例化时就会忽略它,最终定位在唯一符合实例化的实现。
从enable_if的定义可以看出来,当没有指明type类型时,自动为void类型,若指明了类型为_Ty,则会将type赋值为所指定的类型。我们可以通过使用typename std::enable_if<T,_ty>::type
使用enable_if一方面可以增加程序的可读性,另一方面可以提高模板的编译性能。
调用顺序
函数模板具体化和普通函数可同时存在,调用顺序为——普通函数 > 函数模板具体化 > 模板函数。
其中,函数模板具体化是指如果要将某一个或某几个要处理的数据类型进行单独处理,需要额外定义对应数据类型的模板函数,形式是“ template <> void fun(type &t)
。