首页 > 代码库 > C++中模板与泛型编程
C++中模板与泛型编程
目录
- 定义一个通用模板
- 模板特化和偏特化
- 模板实例化与匹配
- 可变参数模板
泛型编程是指独立与任何类型的方式编写代码。泛型编程和面向对象编程,都依赖与某种形式的多态。面向对象编程的多态性在运行时应用于存在继承关系的类,一段代码可以可以忽略基类和派生类之间的差异。在泛型编程中,编写的代码可以用作多种类型的对象。面向对象编程所依赖的多态性称为运行时多态性,泛型编程所依赖的多态性称为编译时多态性或参数式多态性。
1 模板定义
1.1 函数模板
- 模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。
- 模板函数的类型形参跟在关键字 class 或 typename 之后定义.在函数模板形参表中,关键字 typename 和 class 具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用
- 函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前
- 函数模板调用方式。在发生函数模板的调用时,不显示给出模板参数而经过参数推演,称之为函数模板的隐式模板实参调用(隐式调用)在发生函数模板的调用时,显示给出模板参数而不需要经过参数推演,称之为函数模板的显示模板实参调用(显示调用)。显示模板实参调用在参数推演不成功的情况下是有必要的。
- 函数模板与函数重载。函数模板实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表,凡是函数体相同的函数都可以用这个模板来代替,不必定以多个函数。重载函数的参数个数、参数类型或参数顺序3者中必须至少有一种不同,函数返回值类型可以相同也可以不同,函数体可以相同也可以不同。
template<typename T>inline int getMaxVal(const T& t1, const T& t2) { return t1 < t2 ? t2 : t1;}
1.2 类模板
- 类模板也是模板,因此必须以关键字 template 开头,后接模板形参表
- 除了模板形参表外,类模板的定义看起来与任意其他类问相似。类模板可以定义数据成员、函数成员和类型成员,也可以使用访问标号控制对成员的访问,还可以定义构造函数和析构函数等等。
- 与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参,类模板的形参不存在实参推演的问题。
const size_t MAXSIZE = 100;template<class T>class Stack{private: T elements[MAXSIZE];public: //others};
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(){}
template<typename T,int X = 5>inline bool greaterThanX(const T& a) { return a >= X;}std::cout << greaterThanX(10) << std::endl;
template<class T,int MAXSIZE=100>class Stack {private: T elements[MAXSIZE];public: //others};
2.模板特化与偏特化
有时为了需要,针对特定的类型,需要对模板进行特化,也就是特殊处理。 例如,stack类模板针对bool类型,因为实际上bool类型只需要一个二进制位,就可以对其进行存储,使用一个字或者一个字节都是浪费存储空间的.。特化必须在同一命名空间下进行,可以特化类模板也可以特化函数模板,但类模板可以偏特化和全特化,而函数模板只能全特化。模板的偏特化是指需要根据模板的某些但不是全部的参数进行特化。严格的来说,函数模板并不支持偏特化,但由于可以对函数进行重载,所以可以达到类似于类模板偏特化的效果。模板实例化时会优先匹配”模板参数”最相符的那个特化版本。
3 模板实例化与匹配规则
3.1 隐式实例化。在使用模板函数和模板类时,不存在指定类型的模板函数和模板类的实体时,由编译器根据指定类型参数隐式生成模板函数或者模板类的实体称之为模板的隐式实例化。函数模板隐式实例化指的是在发生函数调用的时候,如果没有发现相匹配的函数存在,编译器就会寻找同名函数模板,如果可以成功进行参数类型推演,就对函数模板进行实例化。类模板隐式实例化指的是在使用模板类时才将模板实例化。
2.2 显示实例化。显示实例化也称为外部实例化。在不发生函数调用的时候将函数模板实例化,或者在不适用类模板的时候将类模板实例化称之为模板显示实例化
2.3 匹配规则
4.可变参数模板
可变参数模板是C++11新增的特性之一,它对参数高度泛化,他能表示0到任意个数、任意类型的参数。可变模板参数之前会带有省略号,把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点。可变模版参数和普通的模版参数语义是一致的,所以可以应用于函数和类,即可变模版参数函数和可变模版参数类,然而,模版函数不支持偏特化,所以可变模版参数函数和可变模版参数类展开可变模版参数的方法还不尽相同。
4.1 可变模板参数函数与参数的展开
l 递归函数方式展开参数包。通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的。
l 逗号方式展开参数包
4.2 可变模板参数类与参数展开
可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。具体参考http://www.cnblogs.com/qicosmos/p/4325949.html
可变参数模板类是一个带可变模板参数的模板类,比如C++11中的元祖std::tuple就是一个可变模板类,它的定义如下:
template< class... Types >
class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数:
std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);
可变参数模板的模板参数个数可以为0个,所以下面的定义也是也是合法的:
std::tuple<> tp;
参考:
1. 模板特化和片特化
2. 泛化之美--C++11可变模版参数的妙用
3. 模板参数
template specializations
有些时候统一的一个模板不能解决所有的参数问题,例如统一的一个模板有时候不能编译,不能正确实例化,输出结果有问题
模板特例化:函数模板特化,类模板特化
1.函数模板特化
例如:定义了一个compare的模板函数用比较定义了“<”的对象,如int,double以及自定义类型。
a. 为了处理字符串常量,compare("hi","com"),重载了
template<unsigned N,unsigned M>int compare(const char(&p1)[N],const char(&p2)[M])
注:当比较的字符串长度相同时,GCC 4.83会认为与 int compare(const T& v1,const T& v2) 模糊,无法正确的实例化,编译不通过
test.cpp: In function ‘int main()’:
test.cpp:45:34: error: call of overloaded ‘compare(const char [6], const char [6])’ is ambiguous
cout<<compare("hello","hello")<<endl;
^
test.cpp:45:34: note: candidates are:
test.cpp:14:5: note: int compare(const T&, const T&) [with T = char [6]]
int compare(const T& v1,const T& v2)
^
test.cpp:25:5: note: int compare(const char (&)[N], const char (&)[M]) [with unsigned int N = 6u; unsigned int M = 6u]
int compare(const char(&p1)[N],const char(&p2)[M])
b.为了处理字符串指针 cout<<compare(str1,str2)<<endl;我们特化了 int compare(const T& v1,const T& v2),注意特化的格式以及参数类型。
#include<iostream>#include<cstring>using namespace std;template<typename T>int compare(const T& v1,const T& v2){ if(v1<v2) return -1; if(v2<v1) return 1; return 0;}//Notype template parameter //handle string literals,that type is const char[]template<unsigned N,unsigned M>int compare(const char(&p1)[N],const char(&p2)[M]){ return strcmp(p1,p2);}//special version of compare to handle pointers//to character arraystemplate<>int compare(const char* const& p1,const char* const& p2){ return strcmp(p1,p2);}int main(){ const char* str1="hello"; const char* str2="hello"; cout<<compare(1,2)<<endl; //note:same string length lead to instantiate ambiguously cout<<compare("hello","hell")<<endl; //const char[] cout<<compare(str1,str2)<<endl; return 0;}
2.类模板特化
template <class T>class compare{ public: bool IsEqual(T t1, T t2) { return t1 == t2; }}; int main(){ char str1[] = "Hello"; char str2[] = "Hello"; compare<int> c1; compare<char *> c2; cout << c1.IsEqual(1, 1) << endl; //比较两个int类型的参数 cout << c2.IsEqual(str1, str2) << endl; //比较两个char *类型的参数 return 0;}
template<>class compare<char *> //特化(char*)2 {3 public:4 bool IsEqual(char* t1, char* t2)5 {6 return strcmp(t1, t2) == 0; //使用strcmp比较字符串7 }8 };
C++中模板与泛型编程