C++ 模板学习总结(三)模板参数的三种形式

来源:互联网 发布:网络设备搜索软件 编辑:程序博客网 时间:2024/06/01 10:35

之前的一篇文章中介绍了特化和实例化的知识,那么本次想给大家介绍一下模板的三种参数。

首先呢,要说的是什么是模板参数,那么如果使用过模板的同学会知道在定义一个模板的时候需要在定义上面写上一行诸如template<typename T>这样的代码,那么这行中定义的这个T就是一个模板参数。对于一个模板而言,无论是函数模板还是类模板,都需要对其指定模板参数,可以有多个,但至少要有一个,如果一个都没有那还叫什么模板,当用户在使用这个模板的时候需要指定所有的模板参数以生成一个可用的特化。注意,存在的每一个模板参数在正式使用的时候都必须是被指定的,虽然指定的方法可以不是显式指定,但必须可以推导出来,换句话说编译器必须要能知道每个参数是什么才行。那么对于类模板而言,除了显示指定所有模板参数之外,还可能是该参数有默认值,而对于函数模板显然要复杂的多,模板的参数可以通过函数参数推导而来,这部分被称为模板实参演绎,也是一个非常复杂的议题,我们留之后议。


首先要了解的是虽然我们最常见的模板参数都是类型参数,例如:

template<typename T>class Test{};


在这个简单的示例中,T就是一个类型参数,它的意思是说T表示了一个类型,那么在Test这个class中就可以按照使用一个类型的方式来使用T,比如可以使用T来声明一个实例,或声明一个指针类型,还可以通过它来做类型转换等等,总之一个正常类型可以使用的地方就可以使用T,看看下面的例子

template<typename T>class Test{public:T tObj;T* tPtr;std::list<T> l;T* createInsideObj();};

此例中,我们使用T来创建一个对象还创建一个T的指针对象,除此之外,该类中的createInsideObj方法返回类型也被指定为T*,甚至还可以将T传递给其他的类模板。那么当我们使用这个类模板Test的时候就需要指定T具体的类型到底是什么,例如Test<int>就会将T替换为int,但是有一个原则是不能违背的,就是不能使用T去做它没有定义的工作,例如:

template<typename T>T* Test<T>::createInsideObj(){return new T();}

我们这里给出了createInsideObj的实现,这个例子没有任何实质意义只为说明语法,在定义中使用new T()来创建一个对象,那么仔细观察会发现,该定义要求T类型一定要有一个public的无参构造函数,例如我们使用Test<int>来实例化该类模板

int main(){Test<int> a;int* p= a.createInsideObj();}


这显然是没什么问题的,但是如果我有一下一个类定义

class A{A(){}};

该类A将无参构造函数设置为private的,那么再来试试看,

int main(){Test<A> a;int* p= a.createInsideObj();}

首先,在Test<A> a这一行就会报错,为啥呢,因为Test中定义了一个T类型的tObj,该成员就需要无参构造函数来初始化,而A的无参构造为私有,所以不行,对于第二句同样也不行。那么如果去掉这个tObj的定义,然后在main函数中不去调用createInsideObj这个函数就不会报错了,因为在之前我们将实例化的时候讲过按需编译的概念。

整体上这个概念是比较简单的,所以也就没太多可以讲解的,那么除了这种最常见的类型参数还有其他两种:

非类型模板参数(Non-type template parameters)

模板模板参数(Template template parameters)

除了以上两种,在C++11中还引申出一种新的变长模板参数(Variadic Parameters)的概念,但由于它是基于以上三种的所以就不单独归类了。
我们先来看看非类型模板参数,这种其实和类型模板参数非常相似,不同的是它所指代的不是一个类型而是一个值,先举个最常见的例子:

template<int N>class Test{public:int array[N];};


大家可以看到这个例子中的N就是一个非类型参数,其为int型,而且在Test中被作为一个常量对待。我们可以用一个int值来对其赋值,例如Test<100>,那么此时N就被替换为100。当时在公司讲解此处的时候一位同事提了一个问题说这玩意看起来没啥用啊,为啥这个N值不直接指定呢,这个问题显然是没有理解到模板的作用,对于这个N是一个可变的值,除了此处见到的这个N用来指定数组长度之外,在函数模板中还常常用来控制递归。这个后面遇到再说吧。

需要明白的是,不是所有的类型都可以来指定非类型参数,除了int之外还有其他一些可以被指定为非类型参数的类型,如下:

For integral
For pointers toobjects
For pointers tofunctions
For lvalue referenceparameters
For pointers tomembers
enumeration type
nullptr_t

对此就不一一说明只给出一个例子:

int ai[5];template<const int* pci> struct X {};X<ai> xi;  // ok: array to pointer conversion and cv-qualification conversionstruct Y {void func(){}   };struct  YY {     static Y y;    };Y YY::y;template<const Y* b> struct Z {};Y y;Z<&YY::y> z;  // ok: cv-qualification conversion template<int (&pa)[5]> struct W {};W<ai> w; // ok: no conversionvoid f(char);           void f(int);template<void (*pf)(int)> struct RR {};RR<&f> a; // ok: overload resolution selects f(int)template<void (Y::*pf)()> struct RD {};RD<&Y::func> rd; 


没记错的话,这个例子是来自cppreference的,另外需要说明的只有一点,int (&)[5] 这个类型可能很多人没见过,此类型表示的是一个长度为5的int数组引用类型,而int (&pa)[5]表示的是pa为一个引用对象。顺便也说一下void (Y::*pf)()表示的是Y类中一个返回类型为void无参的成员函数指针,所以可以用&Y::func来对其赋值。个人觉得这个部分的概念相对还是比较简单的,有一点需要说明的是可能有些类型的表示方式我们不太常见,例如T1(T2), T(&)[N]这种的,需要大家自己了解一下,其他的有一点要说明的是,对于指针类型和引用类型,所用来赋值的对象必须是有链接的,《C++ Templates》上说必须要有外部链接,我发现新的g++版本上面内部链接和外部链接都可以,老版本是必须外部链接。

剩下最后一种就是模板的模板参数,这名字念起来就有点拗口,意思就是说其模板参数还是一个模板,我感觉这个看起来比较复杂,其实非常简单,直接来看看例子吧:

template<class T, int a> class A {};template<template<typename, int> class V> class C{public:    V<int, 5> y; // uses the primary template};int main(){    C<A> aa;    return 0;}
那么类模板C的模板参数就是一个 模板模板参数了,使用上面没觉得有什么特别的,所以就不再过多解释了,之后还剩下一个变长模板参数后续再讲,今天先到这里



原创粉丝点击