目录
注:这里讲的是C++11版本
1、介绍:
什么是泛型编程?
泛型编程是指编写与类型无关的通用代码,是代码复用的一种手段
如何实现泛型编程?
C++通过模板函数和模板类来对其进行支持。
2、实现方法
1、函数模板
我们可以定义一个函数模板(function template),而不是为每个类型都定义一个新函数。一个函数模板就是一个公式,可用来生成针对特定类型的函数版本。
定义方法: 模板定义以关键字template开始,后跟一个模板参数列表(template parameter list),这是一个逗号分隔的一个或多个模板参数(template parameter)的列表,用尖括号<>包围起来。
例:
template <typename T>
int compare(const T &v1,const T&2)//其中的T类型由参数类型推导得出
{...}//函数体内容
模板参数列表:作用很像函数的参数列表,函数列表定义了若干特定类型的局部变量,但未指出如何初始化他们。运行时,调用者提供实参来初始化形参。
模板参数:表示在类或函数定义中用到的类型或值。当使用模板时,我们(隐式或显式地)指定模板实参(template arguement),将其绑定到模板参数上。
泛型(这里用T指代):当T被用在参数列表时,会由编译器推导得出,当未被推导出时(例如未在参数列表内使用),则需要在使用时显式对其进行定义,可以使用typename或class来进行声明;
例:
template<typename T,typename U>
int Fun(const T &v1,const T&2){
U a;
std::cout<<a<<std::endl;
...
}
int main()
{
int c=0,d=0;
Fun<string>(c,d);//在调用时显式定义U的类型
return 0;
}
2、类模板
定义:
类模板的定义与函数模板类似,以关键字template开始,后跟一个模板参数列表(template parameter list),这是一个逗号分隔的一个或多个模板参数(template parameter)的列表,用尖括号<>包围起来。
例:
template<class T>
class Foo{
T a;
...
};
注意:当使用一个类模板时,我们必须提供额外信息——显示模板实参(explicit template arguement)列表,它们被绑定到模板参数。编译器使用这些实参来实例化出特定的类。
类模板的成员函数:
与其它任何类相同,我们既可以在类模板内部也可以在其外部为其定义成员函数,且定义在类模板内部的成员函数都被隐式地声明为内联函数。
类模板的成员函数本身是一个普通函数。但是,类模板的每个实例都有其自己版本的成员函数。因此类模板的成员函数具有和模板相同的模板参数,所以,定义在类模板之外的成员函数就必须以关键字template开始,后接类模板参数列表。
与往常一样,当我们在类外定义一个成员时,必须说名成员属于哪个类。而且从一个模板生成的类的名字中必须包含其模板实参。当我们定义一个成员函数时,模板实参与模板形参相同。
template<class T>
class Base{
public:
void print();
...
}
template<class T>
void Base<T>::print(){...}
int main(){
...
}
成员模板:
一个类(无论是普通类还是类模板)可以包含本身是模板的函数。这种成员被称为成员模板(member template)。成员模板不能是虚函数。
普通(非模板)类的成员模板
class Base{
public:
template<typename T>
void print(T a){
cout<<a.count()<<endl;
}
template<typename Q>
void Num(Q q);
};
template<typename Q>
Base::Num(Q q){
cout<<"The number is you know"<<endl;
}
模板默认实参和类模板
template<typename T> class Base {
T a;
public:
template<typename B>
void Size(B a) {
cout << a.size() << endl;
}
template<typename Q>
void Num(Q q);
};
template<class T>
template<typename Q>
void Base<T>::Num(Q q) {
cout << "The number is you know" <<' ' <<a<< endl;
}
类模板和友元:
当一个类包含一个友元声明时,类与友元各自是否是模板是相互无关的。
如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例。
如果友元自身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例。
模板类的友元分三类:
1,非模板友元。
2,约束模板友元,即友元的类型取决于类被实例化时的类型。
3,非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。
在C++标准中,我们可以将模板类型参数声明为友元:
template<class Type>class Bar{ friend Type; }
这样做之后,Foo就是Bar的友元了。
3、C++11可变参数模板
简介
一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类,可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示领个或多个模板参数;函数参数包(function parameter packet),表示零个或多个函数参数。
我们用一个省略号来指出一个模板参数或函数参数表示一个包。在一个模板参数列表中,class...或typename...指出接下来的参数表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数类型是一个模板参数包,则此参数也是一个函数参数包,例如:
//Args是一个模板参数包;rest是一个函数参数包
//Args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template<typename T,typename ...Args>
void foo(const T &t,const Args &...rest);
sizeof...运算符
当我们需要知道包中有多少个元素时,可以sizeof...运算符。类似于sizeof,sizeof...也返回一个常量表达式,而且不会对其实参求值:
template<typename T...Args>void g(Args...args)
{cout<<sizeof...(Args)<<endl;//类型参数的数目
cout<<sizeof...(args)<<endl;//函数参数的数目
}
包扩展:
对于一个参数包,除了获取其大小外,我们能对它做的唯一的事情就是扩展(expand)它。当扩展一个包时,我们还要提供用于每个扩展元素的的模式(pattern)。扩展一个包就是把它分解为构成的元素,对于每个应用模式,获得扩展后的列表。我们通过在模式右边放一个省略号(,,,)来触发扩展操作。
省略号放在前面时表示这个对象要扩展;在声明时省略号放在名字前表示这是一个扩展。
例如,我们的print函数包含两个扩展:
template<typename T,typename... Args>
ostream& print(ostream&os,const &T,const Args&... rest)//扩展Args
{
os<<t<<", ";
return print(os,rest...) //扩展rest
}
第一个扩展操作扩展模板参数包,为print生成函数参数列表。第二个扩展操作出现在对print的调用中。此模式为print调用生成参数列表。
对Args的扩展中,编译器将模式const Args应用到模板参数包Args中的每个元素。因此此模式的扩展结果是一个逗号分隔的零个或多个类型的列表,每个类型都是形如const type&。例如:
print(cout,i,s,42);//包中含有两个参数
此调用被实例化为:
print(ostream&,const int&,const string&,const int&);
第二个扩展发生在对print的(递归)调用中。在此情况下,模式是函数参数包的名字(即rest)。此模式扩展出一个由包中元素组成的,逗号分隔的列表。因此,这个调用等价于:
print(os,s,42);
理解包扩展:
上面print函数仅仅将包扩展为其构成元素,C++还允许更复杂的扩展模式。例如,我们可以编写第二个可变参数函数,对其每个实参调用debug_rep(一个函数),然后调用print打印结果string:
template<typename... Args>
ostream &errorMsg(ostream&os,const Args&... rest)
{
return print(os,debug_rep(rest)...);
}
与之相对,以下代码会报错:
print(os,debug_rep(rest...));
这段代码的问题是我们在debug_rep调用中扩展了rest,它等价于
print(cerr,debug_rep(fcnName,Code.num,otherdata,"otherdata",item));
就是说这样的话就是扩展了函数debug_rep而并不是扩展了print。
扩展中的模式会独立地应用于高中的每个元素。
转发参数包:
在C++11标准下,我们可以组合使用可变参数模板与forwad机制来编写函数,实现将其实参不变地传递给其他的函数。
介绍一下标准库容器的emplace_back成员,它其实就是一个可变参数成员模板,它其实参在容器管理的内存空间中直接构造一个元素。
std::forward<Args>(args)...
这种写法既扩展了模板参数包Args也扩展了函数参数包args,此模式生成如下形式的元素:
std::forward<Ti>(ti);