C++中模板与泛型编程

 目录

    • 定义一个通用模板
    • 模板特化和偏特化
    • 模板实例化与匹配
    • 可变参数模板

  泛型编程是指独立与任何类型的方式编写代码。泛型编程和面向对象编程,都依赖与某种形式的多态。面向对象编程的多态性在运行时应用于存在继承关系的类,一段代码能够能够忽略基类和派生类之间的差别。在泛型编程中,编写的代码能够用做多种类型的对象。面向对象编程所依赖的多态性称为运行时多态性,泛型编程所依赖的多态性称为编译时多态性或参数式多态性。 html


1 模板定义

1.1 函数模板

  • 模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。
  • 模板函数的类型形参跟在关键字 class 或 typename 以后定义.在函数模板形参表中,关键字 typename 和 class 具备相同含义,能够互换使用,两个关键字均可以在同一模板形参表中使用
  • 函数模板能够用与非模板函数同样的方式声明为 inline。说明符放在模板形参表以后、返回类型以前,不能放在关键字 template 以前
  • 函数模板调用方式。在发生函数模板的调用时,不显示给出模板参数而通过参数推演,称之为函数模板的隐式模板实参调用(隐式调用)在发生函数模板的调用时,显示给出模板参数而不须要通过参数推演,称之为函数模板的显示模板实参调用(显示调用)。显示模板实参调用在参数推演不成功的状况下是有必要的。
  • 函数模板与函数重载。函数模板其实是创建一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来表明,凡是函数体相同的函数均可以用这个模板来代替,没必要定以多个函数。重载函数的参数个数、参数类型或参数顺序3者中必须至少有一种不一样,函数返回值类型能够相同也能够不一样,函数体能够相同
1 template<typename T>
2 inline bool isEqual(const T& t1, const T& t2) {
3     return t1 == t2;
4 }

1.2 类模板

  • 类模板也是模板,所以必须以关键字 template 开头,后接模板形参表
  • 除了模板形参表外,类模板的定义看起来与任意其余类问类似。类模板能够定义数据成员、函数成员和类型成员,也可使用访问标号控制对成员的访问,还能够定义构造函数和析构函数等等。
  • 与调用函数模板造成对比,使用类模板时,必须为模板形参显式指定实参,类模板的形参不存在实参推演的
1 const size_t MAXSIZE = 100;
2 template<class T>
3 class Stack{
4 private:
5     T elements[MAXSIZE];
6 public:
7     //others
8 };

1.3 模板参数

  • 类型模板形参:类型形参由关见字class或typename后接说明符构成,如template<class T> void getMaxVal(const T& a,const T& b){};其中T就是一个类型形参,类型形参的名字由用户自已肯定。
  • 非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int X> greaterThanX(const T& a);其中int X就是非类型的模板形参。非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。非类型的模板参数是有限制的,通常是一个整型,它们能够是常整数(包括枚举类型)或者指向外部连接对象的指针。浮点数和类对象是不容许做为非类型模板参数的
  • 模板的默认参数。能够为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板均可觉得模板的非类型形参提供默认参数。类模板类型形参默认值和函数的默认参数同样,若是有多个类型形参则从第一个形参设定了默认值以后的全部模板形参都要设定默认值。类模板的类型形参默认值形式为:template<class T1, class T2=int> class A{};为第二个模板类型形参T2提供int型的默认值,在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。好比template<class  T1, class T2=int> class A{public: void h();}; 定义方法为template<class T1,class T2> void A<T1,T2>::h(){}
 1 template<typename T,int X = 5>
 2 inline bool isEqualToX(const T& a) {
 3     return a == X;
 4 }
 5 
 6 template<class T,int MAXSIZE=100>
 7 class Stack {
 8 private:
 9     T elements[MAXSIZE];
10 public:
11     //others
12 };

2.模板特化与偏特化

  有时为了须要,针对特定的类型,须要对模板进行特化,也就是特殊处理。 例如,stack类模板针对bool类型,由于实际上bool类型只须要一个二进制位,就能够对其进行存储,使用一个字或者一个字节都是浪费存储空间的.。特化必须在同一命名空间下进行,能够特化类模板也能够特化函数模板,但类模板能够偏特化和全特化,而函数模板只能全特化。模板的偏特化是指须要根据模板的某些但不是所有的参数进行特化。严格的来讲,函数模板并不支持偏特化,但因为能够对函数进行重载,因此能够达到相似于类模板偏特化的效果。模板实例化时会优先匹配”模板参数”最相符的那个特化版本。template < >告诉编译器这是一个特化的模板ios

 1 template<class T,int MAXSIZE=100>
 2 class Stack {
 3 private:
 4     T elements[MAXSIZE];
 5 public:
 6     //others
 7 };
 8 
 9 //template specializations aim at bool
10 template<>
11 class Stack<bool>{
12 
13 };
 
1
template<typename T> 2 inline bool isEqual(const T t1, const T t2) { 3 return t1 == t2; 4 } 5 6 //针对int型的指针作特化 7 template<> 8 inline bool isEqual(const int* p1,const int* p2){ 9 return *p1 == *p2; 10 }

类模板的偏特化,例如c++标准库中的类vector的定义,这个偏特化的例子中,一个参数被绑定到bool类型,而另外一个参数仍未绑定须要由用户指定。c++

1 template <class T, class Allocator>
2 class vector { //// };
3 template <class Allocator>
4 class vector<bool, Allocator> { ////};

函数模板的偏特化,严格的来讲,函数模板并不支持偏特化,但因为能够对函数进行重载,因此能够达到相似于类模板偏特化的效果。根据重载规则,对(a)进行重载。若是将(a)称为基模板,那么(b)称为对基模板(a)的重载,而非对(a)的偏特化。C++的标准委员会仍在对下一个版本中是否容许函数模板的偏特化进行讨论。
template <class T> void f(T); (a)
template < class T> void f(T*); (b)编程


3 模板实例化与匹配规则

3.1 隐式实例化。在使用模板函数和模板类时,不存在指定类型的模板函数和模板类的实体时,由编译器根据指定类型参数隐式生成模板函数或者模板类的实体称之为模板的隐式实例化。函数模板隐式实例化指的是在发生函数调用的时候,若是没有发现相匹配的函数存在,编译器就会寻找同名函数模板,若是能够成功进行参数类型推演,就对函数模板进行实例化。类模板隐式实例化指的是在使用模板类时才将模板实例化。函数

3.2 显示实例化。显示实例化也称为外部实例化。在不发生函数调用的时候将函数模板实例化,或者在不适用类模板的时候将类模板实例化称之为模板显示实例化。对于函数模板而言,不论是否发生函数调用,均可以经过显示实例化声明将函数模板实例化,定义函数模板为:template函数返回类型 函数模板名<实际类型列表>(函数参数列表),显示实例化为template void func<int>(const int&);类模板的显示实例化,对于类模板而言,不论是否生成一个模板类的对象,均可以直接经过显示实例化声明将类模板实例化,定义类模板格式为:template class 类模板名<实际类型列表>,显示实例化为template class theclass<int>;优化

3.3 匹配规则spa

(1) 类模板的匹配规则。最优化的优于次特化的,即模板参数最精确匹配的具备最高的优先权,每一个类型均可以用做普通型(a)的参数,但只有指针类型才能用做(b)的参数,而只有void*才能做为(c)的参数。.net

template <class T> class vector{//…//}; // (a) 普通型
template <class T> class vector<T*>{//…//}; // (b) 对指针类型特化
template <> class vector <void*>{//…//}; // (c) 对void*进行特化指针

(2) 函数模板的匹配规则。非模板函数具备最高的优先权。若是不存在匹配的非模板函数的话,那么最匹配的和最特化的函数具备高优先权rest

template <class T> void f(T); // (d)
template <class T> void f(int, T, double); // (e)
template <class T> void f(T*); // (f)
template <> void f<int> (int) ; // (g)
void f(double); // (h)
bool b;
int i;
double d;
f(b); // 以 T = bool 调用 (d)
f(i,42,d) // 以 T = int 调用(e)
f(&i) ; // 以 T = int* 调用(f)
f(d); // 调用(g)


4.可变参数模板

参考:http://www.cnblogs.com/qicosmos/p/4325949.html

       可变参数模板是C++11新增的特性之一,它对参数高度泛化,他能表示0到任意个数、任意类型的参数。可变模板参数以前会带有省略号,把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。咱们没法直接获取参数包args中的每一个参数的,只能经过展开参数包的方式来获取参数包中的每一个参数,这是使用可变模版参数的一个主要特色。可变模版参数和普通的模版参数语义是一致的,因此能够应用于函数和类,便可变模版参数函数和可变模版参数类,然而,模版函数不支持偏特化,因此可变模版参数函数和可变模版参数类展开可变模版参数的方法还不尽相同。

4.1 可变模板参数函数与参数的展开

  • 递归函数方式展开参数包。经过递归函数展开参数包,须要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的。
  • 逗号方式展开参数包
 1 #include <iostream>
 2 using namespace std;
 3 //递归终止函数
 4 void print()
 5 {
 6    cout << "empty" << endl;
 7 }
 8 //展开函数
 9 template <class T, class ...Args>
10 void print(T head, Args... rest)
11 {
12    cout << "parameter " << head << endl;
13    print(rest...);
14 }
15 
16 
17 int main(void)
18 {
19    print(1,2,3,4);
20    return 0;
21 }
 1 template <class T>
 2 void printarg(T t)
 3 {
 4    cout << t << endl;
 5 }
 6 
 7 template <class ...Args>
 8 void expand(Args... args)
 9 {
10    int arr[] = {(printarg(args), 0)...};
11 }
12 
13 expand(1,2,3,4);

4.2 可变模板参数类与参数展开

  可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不一样,可变参数模板类的参数包展开须要经过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。可变参数模板类是一个带可变模板参数的模板类,好比C++11中的元祖std::tuple就是一个可变模板类,它的定义以下,这个可变参数模板类能够携带任意类型任意个数的模板参数。

1 template< class... Types >
2 class tuple; 
3 std::tuple<int> tp1 = std::make_tuple(1);
4 std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
5 std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);
6 std::tuple<> tp;//可变参数模板的模板参数个数能够为0个,因此下面的定义也是也是合法的:

参考

  1. 模板特化和偏特化
  2. 泛化之美--C++11可变模版参数的妙用
相关文章
相关标签/搜索