一、模板与多态
多态就是通过单一命名标记关联不同特定行为的能力。在C++中,主要是通过继承和虚函数来实现,由于继承和虚函数主要是在运行期进行处理,因此c++把这种多态称为“动多态”。而通过函数重载方式也可以单一命名标记关联不同行为,也是多态,这种方式是在编译期进行处理的,因此称为“静多态”。
在前文就阐述过,函数模板就是一个家族函数,这就意味着它是具有单一命名标记关联不同特定行为的能力。其实从泛的层面上来说,无论是函数模板或是类模板,其本身都具备多态能力,并还可以向普通函数或类那样支持到重载、继承、虚函数,其多态特性就具有更多层次性和编码样式。
1.1 函数模板本身就是静多态
普通函数通过重载实现多态,定义了一组同函数名但参数类型及数量不同的函数,在实际函数调用时,编译器由传入的实参去推演采用哪个函数,从而实现不同特定行为的能力
void doit(const int &val){std::cout<<"doit int\n";};
void doit(const char &val){std::cout<<"doit char\n";};
void doit(const double &val){std::cout<<"doit double\n";};
void doit(const char &cval, const int &ival){std::cout<<"doit char and int\n";};
//
doit(10);
doit(2.5);
doit('a',100);
而函数模板同样能达到此效果,支持特定行为更多,并更简洁:
template <typename T>
void doit_template(const T &val){std::cout<<"doit_template const T\n";};
template <typename T>
void doit_template(const T *val){std::cout<<"doit_template const T*\n";}; //函数模板重载
template <typename T1, typename T2>
void doit_template(const T1 &val1,const T2 &val2){std::cout<<"doit_template const T1,T2\n";};//函数模板重载
//
doit_template(10);
doit_template(2.5);
doit_template('a',100);
//
typedef void (*pdoit)(const int &val);
pdoit pf = &doit;
doit_template<pdoit>(pf);
在看看下面这个例子,通过"..."省略号参数支持,实现不定参数集:
//
template <typename T>
void doit_arg(const T &val,...){std::cout<<"doit_arg const T ...\n";};
//
doit_arg(1,2,3);
然后也将该函数进行重载:
//
template <typename T>
void doit_arg(const T &val,...){std::cout<<"doit_arg const T ...\n";};
template <typename T>
void doit_arg(const T &val){std::cout<<"doit_arg const T \n";};
//
doit_arg(1,2,3); //OK
doit_arg(1); //error,两个函数模板都满足,编译器无法推演
但采用普通函数去重载,调用时是能识别的,因为普通函数比模板函数更具有确定性,编译器会优先精确匹配到它,注意该函数并不是模板函数的特例化(它没带模板标记)。另外如果是在模板之间选择匹配的话,特化程度更高的模板优先被选择。
//
template <typename T>
void doit_arg(const T &val,...){std::cout<<"doit_arg const T ...\n";};
template <typename T>
void doit_arg(const T &val){std::cout<<"doit_arg const T \n";};
void doit_arg(const double &val){std::cout<<"doit_arg const double \n";};
//
doit_arg(1,2,3); //OK
//doit_arg(1); //error
doit_arg(1.2); //OK
不同于普通类通过继承及虚函数来实现多态,类模板本身能实现多态,从实现方式来看,它可以归并在静多态方面。
template <typename T>
class ATest //类模板本身能实现多态
{
public:
T val; //可以传入自定义的类型,将多态性封装在T来实现
};
//
ATest<int> i_c;
ATest<float> f_c;
ATest<std::string> s_c;
1.2 模板的动多态
类模板和普通模板一样,是可以被继承的,但有和普通模板不一样,类模板的继承有更多的设计选择。
template <typename T>
class ATest //类模板本身能实现多态
{
public:
T val; //可以传入自定义的类型,将多态性封装在T来实现
};
template <typename T>
class AChild1 : public ATest<T>
{
public:
T c_val;
};
template < typename T=int>
class AChild2 : public ATest<int>
{
public:
int c_val;
};
class AChild3 : public ATest<char>
{
public:
char c_val;
};
template < typename T1,typename T2>
class AChild4 : public ATest<T1>
{
public:
T2 c_val;
};
template <typename T>
class AChild5 : virtual public ATest<T>
{
public:
T c_val;
};
template <typename T>
class AChild6 : private ATest<T>
{
public:
T c_val;
};
//
AChild1<double> ichild1;
ichild1.val = 10.2;
ichild1.c_val = 11.5;
//
AChild2<> ichild2;
ichild2.val = 10;
ichild2.c_val = 15;
//
AChild3 ichild3;
ichild3.val = 'a';
ichild3.c_val = 'b';
//
AChild4<int,float> ichild4;
ichild4.val = 10;
ichild4.c_val = 20.3;
//
AChild5<int> ichild5;
ichild5.val = 1;
ichild5.c_val = 5;
//
AChild6<float> ichild6;
//ichild6.val = 2.6; //私继承
ichild6.c_val = 5.9;
仅仅就是类继承这一特点,类模板就能演化成不同的继承设计,支持特例化继承、virtual继承、私继承、缺省默认模板参数继承、模板参数扩展继承等等。
模板参数也是类型,那么在继承实现中,可以继承模板参数
template <typename T>
class AChild7 : public T
{
public:
AChild7():T()
{
}
~AChild7()
{
}
};
template <typename T>
class AChild8 : public T,public ATest<T> //多重继承
{
public:
AChild8():T()
{
}
~AChild8()
{
}
};
//
template <typename T,typename Base>
class AChild10 : public Base,public ATest<T> //多重继承
{
public:
AChild10():Base(),ATest<T>()
{
}
~AChild10()
{
}
};
//
AChild7<std::string> ichild7;
ichild7.append("hello");
std::cout<<"ichild7 size = "<<ichild7.size() <<std::endl;
//
AChild8<std::string> ichild8;
ichild8.val = "hi";
但将模板参数作为基类来继承,也会引入风险,就是前文讲述的,使用者需要自行把控传入模板参数类型的行为,例如上述继承中,传入一个int型(原生类型等)
AChild7<int> ichild7_; //error: base type 'int' fails to be a struct or class type
另外,为基类模板参数设置默认缺省值,派生类在不指定类型时,就能实现继承。
template <typename T=int>
//template <typename T>
class ATest //类模板本身能实现多态
{
public:
T val; //可以传入自定义的类型,将多态性封装在T来实现
};
//下面三种继承是一致的
template < typename T=int>
class AChild2 : public ATest<int>
{
public:
int c_val;
};
class AChild3 : public ATest<char>
{
public:
char c_val;
};
class AChild9 : private ATest<>
{
public:
};
既然基类能作为模板参数,那么派生类用来作为模板参数呢,这种倒施逆行的方式在一些特定场景可能会有奇效哦。
template <typename Derived>
class MyBase
{
public:
void doSomething(Derived val)
{
val.doSomething();
};
};
class MyDerived : public MyBase<MyDerived>
{
public:
void doSomething(void)
{
std::cout<< "just boring!" <<std::endl;
};
};
//
MyDerived myd;
myd.doSomething();
类模板和普通类一样,支持到普通函数成员虚拟化设计,但函数模板成员不能声明为虚函数,因为虚函数调用机制实现使用了一个大小固定的表,每个虚函数都对应表的一个入口,但成员函数模板在构建虚函数表之前是无法确定的。
//模板-虚函数
template <typename T>
class VClass
{
public:
virtual void doit()
{
std::cout<< "VClass doit"<<std::endl;
};
};
template <typename T>
class MYVBase : public VClass<T>
{
public:
virtual void doit()
{
std::cout<< "MYVBase doit"<<std::endl;
};
};
template <typename T>
class MYVBase_C : public VClass<T>
{
public:
};
//
MYVBase<int> vbc;
vbc.doit(); //MYVBase doit
MYVBase_C<char> vbc_c;
vbc_c.doit(); //VClass doit
或者将基类作为派生类的模板参数,同样能实现虚函数效果
//模板-虚函数-另类
class VirtualClass
{
public:
virtual void doit()
{
std::cout<< "VirtualClass doit"<<std::endl;
};
};
class NVirtualClass
{
public:
};
template <typename VBase>
class MYNewVBase : public VBase
{
public:
void doit()
{
std::cout<< "MYNewVBase doit"<<std::endl;
};
};
template <typename VBase>
class MYNewVBase_C : public VBase
{
public:
};
//
MYNewVBase<VirtualClass> nvbc;
nvbc.doit();
MYNewVBase_C<VirtualClass> nvbc_c;
nvbc_c.doit();
MYNewVBase<NVirtualClass> nvbc_;
nvbc_.doit();
二、模板元编程
模板实例化机制是一种递归语言机制,可以用于在编译期执行复杂计算,这种模板实例化所展现的编译器计算被叫做template meta programming。看下面的例子:
//
template <int N>
class POW2
{
public:
enum{ret=2*POW2<N-1>::ret};
};
template <>
class POW2<0>
{
public:
enum{ret=1};
};
//
std::cout<<"POW2<5>::ret = "<< POW2<5>::ret <<std::endl; //ret = 32
上面展示的是通过递归机制在编译期计算2的N次幂。编译器在实例化时,会不断递归下去,直到特例化POW2<0>结束。注意,枚举值POW2<5>::ret=32是编译期就确定下来的。替换静态常量也能达到同样效果。
模板元编程可以通过展开循环来优化迭代效率,例如我们要计算两个数组的点乘,通常做法是:
for(int i=0; i<N; i++)
{
result += a[i]*b[i];
}
对于N很大的情况,其实这种循环计算效率是很慢的,但是如果在编译期展开计算表达式,在运行期直接计算,那么运行效率是很高的,这方面类似我们在嵌入式领域时对于字符编码、图像矩阵等就常常采用展开的方式,当然那些很多展开都是借助辅助工具自动创建的:
retsult = a[0]*b[0]+a[1]*b[1]+...+a[N-1]*b[N-1];
对于我们自行编码时,如果我们手动重复撰写这些计算公式展开肯定很乏味,采用模板元编程可以解决这些烦恼
//基本模板
template <int DN, typename T>
class dotMultip{
public:
static T retsult (T* a, T* b)
{
return (*a)*(*b)+dotMultip<DN-1,T>::retsult(a+1,b+1);
};
};
//特例化,循环结束
template < typename T>
class dotMultip<1,T>{
public:
static T retsult (T* a, T* b)
{
return (*a)*(*b);
}
};
//辅助函数,初次调用进入循环
template <int DN, typename T>
inline T dotMultipFunc(T* a, T* b){
return dotMultip<DN,T>::retsult(a,b);
};
//
int a[5]={1,2,3,4,5};
int b[5]={2,3,4,5,6};
std::cout<<"dotMultipFunc<5>(a,b) = "<< dotMultipFunc<5>(a,b) <<std::endl;//=70
在实例化过程中,dotMultipFunc<5>(a,b)的调用时如下过程展开:
dotMultipFunc<5>(a,b)
=dotMultip<5,int>::retsult(a,b)
=(*a)*(*b)+dotMultip<4,int>::retsult(a+1,b+1)
=(*a)*(*b)+(*(a+1))*(*(b+1))+dotMultip<3,int>::retsult(a+2,b+2)
=(*a)*(*b)+(*(a+1))*(*(b+1))+(*(a+2))*(*(b+2))+dotMultip<2,int>::retsult(a+3,b+3)
=(*a)*(*b)+(*(a+1))*(*(b+1))+(*(a+2))*(*(b+2))+(*(a+3))*(*(b+3))+dotMultip<1,int>::retsult(a+4,b+4)
=(*a)*(*b)+(*(a+1))*(*(b+1))+(*(a+2))*(*(b+2))+(*(a+3))*(*(b+3))+(*(a+4))*(*(b+4))
当然,这运行效率的提高是有代价的,首先,数组的元数在编译期是已知的,并由于编译期展开的原因,可能会带来编译效率的下降,至于是否需要选用,取决实际项目中要求的权衡了。
三、模板与指针
智能指针,本质上通过类模板可以有效的封装内存操作,管理使用基础对象的操作,能够动态分配和释放相关继承类的对象,这就是我们所描述的智能指针。
//智能指针示例
template < typename T>
class Handle
{
public:
Handle()
:ptr(0)
{
};
explicit Handle(T*p)
:ptr(p)
{
};
~Handle(){
delete ptr;
ptr = 0;
std::cout<<"~Handle()\n";
};
//拷贝构造
Handle(Handle<T> const&rhs)
{
//delete ptr;
ptr = rhs.ptr;
};
//拷贝赋值运算
Handle<T>& operator=(const Handle &rhs)
{
delete ptr;
ptr = rhs.ptr;
return *this;
};
//指针运算符
T& operator*() const
{
assert(0!=ptr);
return *ptr;
};
//指针运算符
T* operator->() const
{
assert(0!=ptr);
return ptr;
};
private:
T *ptr;
//
};
//
//
Handle<MYNewVBase_C<VirtualClass> > first_hclass(new MYNewVBase_C<VirtualClass>());
first_hclass->doit();
(*first_hclass).doit();
Handle<MYNewVBase_C<VirtualClass> > second_hclass = first_hclass;
second_hclass->doit();
上述例子,通过模板参数typename T实现对实体类指向,并给Handle模板赋予指针运算符“*”和"&",使得使用实体类T时,和以其本身实例化指针时使用是一致的。
四、源码
建立test.h/cpp源文件,g++ test.cpp -o test.exe编译输出,测试如下:
test.h
#ifndef _TEST_H_
#define _TEST_H_
#include <iostream>
#include <string>
#include <stdio.h>
#include <cassert>
//1.1 函数模板本身就是静多态
void doit(const int &val){std::cout<<"doit int\n";};
void doit(const char &val){std::cout<<"doit char\n";};
void doit(const double &val){std::cout<<"doit double\n";};
void doit(const char &cval, const int &ival){std::cout<<"doit char and int\n";};
template <typename T>
void doit_template(const T &val){std::cout<<"doit_template const T\n";};
template <typename T>
void doit_template(const T *val){std::cout<<"doit_template const T*\n";}; //函数模板重载
template <typename T1, typename T2>
void doit_template(const T1 &val1,const T2 &val2){std::cout<<"doit_template const T1,T2\n";};//函数模板重载
//
template <typename T>
void doit_arg(const T &val,...){std::cout<<"doit_arg const T ...\n";};
template <typename T>
void doit_arg(const T &val){std::cout<<"doit_arg const T \n";};
void doit_arg(const double &val){std::cout<<"doit_arg const double \n";};
//1.2 模板的动多态
template <typename T=int>
//template <typename T>
class ATest //类模板本身能实现多态
{
public:
T val; //可以传入自定义的类型,将多态性封装在T来实现
};
template <typename T>
class AChild1 : public ATest<T>
{
public:
T c_val;
};
template < typename T=int>
class AChild2 : public ATest<int>
{
public:
int c_val;
};
class AChild3 : public ATest<char>
{
public:
char c_val;
};
template < typename T1,typename T2>
class AChild4 : public ATest<T1>
{
public:
T2 c_val;
};
template <typename T>
class AChild5 : virtual public ATest<T>
{
public:
T c_val;
};
template <typename T>
class AChild6 : private ATest<T>
{
public:
T c_val;
};
template <typename T>
class AChild7 : public T
{
public:
AChild7():T()
{
}
~AChild7()
{
}
};
template <typename T>
class AChild8 : public T,public ATest<T>
{
public:
AChild8():T()
{
}
~AChild8()
{
}
};
class AChild9 : private ATest<>
{
public:
};
//
template <typename T,typename Base>
class AChild10 : public Base,public ATest<T> //多重继承
{
public:
AChild10():Base(),ATest<T>()
{
}
~AChild10()
{
}
};
template <typename Derived>
class MyBase
{
public:
void doSomething(Derived val)
{
val.doSomething();
};
};
class MyDerived : public MyBase<MyDerived>
{
public:
void doSomething(void)
{
std::cout<< "just boring!" <<std::endl;
};
};
template <typename Derived>
class MyCBase
{
public:
void doSomething(Derived val)
{
val.doSomething();
};
};
template <typename T>
class MyCDerived : public MyCBase<MyCDerived<T> >
{
public:
void doSomething(const T &val)
{
std::cout<< "just boring =" << val <<std::endl;
};
};
//模板-虚函数
template <typename T>
class VClass
{
public:
virtual void doit()
{
std::cout<< "VClass doit"<<std::endl;
};
};
template <typename T>
class MYVBase : public VClass<T>
{
public:
virtual void doit()
{
std::cout<< "MYVBase doit"<<std::endl;
};
};
template <typename T>
class MYVBase_C : public VClass<T>
{
public:
};
//模板-虚函数-另类
class VirtualClass
{
public:
virtual void doit()
{
std::cout<< "VirtualClass doit"<<std::endl;
};
};
class NVirtualClass
{
public:
};
template <typename VBase>
class MYNewVBase : public VBase
{
public:
void doit()
{
std::cout<< "MYNewVBase doit"<<std::endl;
};
};
template <typename VBase>
class MYNewVBase_C : public VBase
{
public:
};
//模板元编程
template <int N>
class POW2
{
public:
enum{ret=2*POW2<N-1>::ret};
static int const retsult=2*POW2<N-1>::retsult;
};
template <>
class POW2<0>
{
public:
enum{ret=1};
static int const retsult=1;
};
//基本模板
template <int DN, typename T>
class dotMultip{
public:
static T retsult (T* a, T* b)
{
return (*a)*(*b)+dotMultip<DN-1,T>::retsult(a+1,b+1);
};
};
//特例化,循环结束
template < typename T>
class dotMultip<1,T>{
public:
static T retsult (T* a, T* b)
{
return (*a)*(*b);
}
};
//辅助函数,初次调用进入循环
template <int DN, typename T>
inline T dotMultipFunc(T* a, T* b){
return dotMultip<DN,T>::retsult(a,b);
};
//智能指针示例
template < typename T>
class Handle
{
public:
Handle()
:ptr(0)
{
};
explicit Handle(T*p)
:ptr(p)
{
};
~Handle(){
delete ptr;
ptr = 0;
std::cout<<"~Handle()\n";
};
//拷贝构造
Handle(Handle<T> const&rhs)
{
//delete ptr;
ptr = rhs.ptr;
};
//拷贝赋值运算
Handle<T>& operator=(const Handle &rhs)
{
delete ptr;
ptr = rhs.ptr;
return *this;
};
//指针运算符
T& operator*() const
{
assert(0!=ptr);
return *ptr;
};
//指针运算符
T* operator->() const
{
assert(0!=ptr);
return ptr;
};
private:
T *ptr;
//
};
#endif //_TEST_H_
test.cpp
#include "test.h"
int main(int argc, char* argv[])
{
doit(10);
doit(2.5);
doit('a',100);
//
doit_template(10);
doit_template(2.5);
doit_template('a',100);
typedef void (*pdoit)(const int &val);
pdoit pf = &doit;
doit_template<pdoit>(pf);
//
doit_arg(1,2,3);
//doit_arg(1);
doit_arg(1.2);
//
ATest<int> i_c;
ATest<float> f_c;
ATest<std::string> s_c;
//
AChild1<double> ichild1;
ichild1.val = 10.2;
ichild1.c_val = 11.5;
//
AChild2<> ichild2;
ichild2.val = 10;
ichild2.c_val = 15;
//
AChild3 ichild3;
ichild3.val = 'a';
ichild3.c_val = 'b';
//
AChild4<int,float> ichild4;
ichild4.val = 10;
ichild4.c_val = 20.3;
//
AChild5<int> ichild5;
ichild5.val = 1;
ichild5.c_val = 5;
//
AChild6<float> ichild6;
//ichild6.val = 2.6; //私继承
ichild6.c_val = 5.9;
//
AChild7<std::string> ichild7;
ichild7.append("hello");
std::cout<<"ichild7 size = "<<ichild7.size() <<std::endl;
//
AChild8<std::string> ichild8;
ichild8.val = "hi";
//
//AChild7<int> ichild7_; //error: base type 'int' fails to be a struct or class type
//
MyDerived myd;
myd.doSomething();
//
MyCDerived<int> mycd;
mycd.doSomething(100);
//
MYVBase<int> vbc;
vbc.doit();
MYVBase_C<char> vbc_c;
vbc_c.doit();
//
MYNewVBase<VirtualClass> nvbc;
nvbc.doit();
MYNewVBase_C<VirtualClass> nvbc_c;
nvbc_c.doit();
MYNewVBase<NVirtualClass> nvbc_;
nvbc_.doit();
//
std::cout<<"POW2<5>::ret = "<< POW2<5>::ret <<std::endl;
std::cout<<"POW2<5>::retsult = "<< POW2<5>::retsult <<std::endl;
//
int a[5]={1,2,3,4,5};
int b[5]={2,3,4,5,6};
std::cout<<"dotMultipFunc<5>(a,b) = "<< dotMultipFunc<5>(a,b) <<std::endl;
//
Handle<MYNewVBase_C<VirtualClass> > first_hclass(new MYNewVBase_C<VirtualClass>());
first_hclass->doit();
(*first_hclass).doit();
Handle<MYNewVBase_C<VirtualClass> > second_hclass = first_hclass;
second_hclass->doit();
return 0;
};