把以前写的C++模板的应用心得发表出来。回想起当时在学习C++模板时的无助和恐惧,现在还心有余悸,我分享出来我的心得,只希望别人少走弯路,其实它就这么几种用法,不需要害怕。
我总结了模板的四种使用方式,基本覆盖了大部分的模板使用场景,了解了这四种方式,就可以在看其它代码时理解别人为什么会在这个地方用模板。
std::vector
std::vector
template class Singleton
{
protected:
Singleton(){}
public:
static T& GetInstance()
{
static T instance;
return instance;
}
};
Class CMySingleton : public Singleton< CMySingleton >
class A1
{
public: void fun();
};
class A2
{
public: void fun();
};
template class CFunInvoker
{ public:
Static void invoke(A* t)
{ t->fun(); }
}
A1 a1;
A2 a2;
CFunInvoker::invoke(&a1);
CFunInvoker::invoke(&a2);
A1,A2两个类,都有一个fun的函数。另一个调用者CFunInvoker需要调用这两个类的fun函数。上面这个例子,A1和A2并没有什么关联,它们只需要提供一个名为fun参数为空的函数就可以被调用了。而调用者CFunInvoker对于被调用者的要求也就是有这样一个函数就行。仅仅能过约定好函数名和参数的方式就可以实现对A1,A2,CFunInvoker 几乎完全的解耦。
如果用动多态实现的话,那就需要A1和A2继承自同一个含有虚接口fun的父类(比如这个父类叫CFunBase)。并且对于CFunInvoker来说,它需要定义一个这样的父类指针(CFunBase*),并对其进行调用。这个时候,A1和A2就不那么自由了,任何对CFunBase的修改都会影响到A1和A2的功能。这样A1,A2,CFunInvoker的耦合性变高了,它们需要的是一个类来实现关联。
因此,静多态的好处就是:静多态不需要实现多态的类型有公共的基类,因为它可以一定程度上的解耦,但是它仍然需要模板类与模板参数之间有一些协议(这里协议就比如上面的例子中需要名为fun参数为空的函数)。
但如果有些模板参数类型不满足这些协义,怎么办?比如我想调用CFunInvoker
因此我们引入静多态的另一个用处:Traits(粹取)
比如下面这个Host类需要模板参数类型提供一个叫dosomething的方法,所以Host是可以编译通过,但Host
为了解决这个问题,我们增加一个Traits类,它一定会对外提供一个dosomething的方法。对于普通类型,它就转发这个方法,于对int型,它作了特化,实现了一个空的dosomething的方法。因此无论是Host
STL中大量运用了traits。比如我们常见的string类型,别以为它只能处理字符串,它可以处理任何类型,你甚至可以用它来处理二进制的buffer(binaryarray)。
比如我们可以修改std::string让其内部处理long类型,让它成为一个long型数组。
typedef
basic_string, allocator > longstring;
longstring strlong;
strlong.push_back(23);
strlong.push_back(4562);
long arrLong[2] = {23, 4562};
longstring strlongFromArr(arrLong, ARRAYSIZE(arrLong));
assert(strlong == strlongFromArr);
模板类的很多应用在于它能针对不同的模板参数生成不同的类。这使得我们可以通过模板类将函数指针以及它的参数类型记录下来,在需要的时候再对函数进行调用。
基于函数转发的应用有很多
下面模拟一个简单的转发
template class function;
template
class function
{
public:
typedef R(*fun)(A0 );
function(fun ptr):m_ptr(ptr){}
R operator()(A0 a)
{(*m_ptr)(a);}
fun m_ptr;
};
int testfun(int a)
{
printf("%d", a);
return 2;
}
function f1(&testfun);
f1(4);
上面的例子把函数testfun的函数指针,以及它的函数签名int (int)作为模板参数保存在了f1这个对象中。在需要的时候,就可以用f1对这个函数进行调用。
下面的例子模拟了类成员函数的转发
template class function;
template
class function
{
public:
typedef R(T::*fun)(A0);
function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){}
R operator()(A0 a) {(m_pThis->*m_ptr)(a);}
fun m_ptr;
T* m_pThis;
};
class CA
{
public:
void Fun(int a) {cout << a;}
};
CA a;
function f(&CA::Fun, &a);
f(4); // 等价于a.Fun(4);
上面的例子把class CA的对象指针,成员函数指针,以及它的成员函数签名
void (CA::*)(int)
作为模板参数保存在了f这个对象中。在需要的时候,就可以用f对这个对象的这个成员函数函数进行调用。
调用的方式很简单
f(4); // 等价于a.Fun(4);
就像是调用一个普通的C函数一样。CA类对象不见了,.或->操作符不见了。函数转发实现了一层层的封装与绑定,最终上调用者与CA类型隔离,实现了解耦。
不过函数转发的这种封装使会使得调用效率降低,如何让封装后的调用像普通函数调用一样快,请参考我发的另一篇学习心得
高效C++委托的原理
prinft("%d",5050);
struct is_void
{
enum{value = false;}
}
template<>
struct is_void
{
enum{value = true;}
}
std::cout << is_void //显示false
template
struct Conversion
{
static char Test(U);
static long Test(...);
static T MakeT();
enum { exists =
(sizeof(Test(MakeT())) == sizeof(char) )};
};
class A;
class B: public A;
printf("%d, %d", Conversion::exists, Conversion::exists);
输出1,0
上面的例子通过重载决议和sizeof取得重载函数Test的返回值大小,再通过枚举常量exists在编译期保存。
Conversion
中,重载决议采用的是char Test(A*)方法,因此Conversion::exists为1。
Conversion
中,重载决议采用的是long Test(...)方法,因此Conversion::exists为0。
typedef struct NULL_TYPE{} NullType
template
struct Typelist
{
typedef T Head;
typedef U Tail;
}
typedef Typelist>> mytypelist ;
template
struct Typelist
{
typedef T Head;
typedef U Tail;
Head m_head;
Tail m_tail;
}
Typelist> storage;
Storage. m_head = ClassA();
Storage.m_tail.m_head = ClassB();
void * CreateObj(const std::string & strClsName)
{
if (strClsName == “ClassA")
{
return new ClassA();
}
else if (strClsName == " ClassB")
{
return new ClassB();
}
else if (strClsName == " ClassC")
{
return new ClassC();
}
}
class ClassA
{
public;
virtual const char* getClassName(){ return m_classname;}
static char* m_classname; //每个类型用一个字符串来表示自己的型别
};
char* ClassA::m_classname = “ClassA”;
class ClassB …
class ClassC …
typedef
Typelist
>
>
mytypelist ;
template
struct Typelist
{
typedef T Head;
typedef U Tail;
static void* CreatObj(const char *pName)
{
if (strcmp(Head::m_classname, pName) == 0 )
{
return new Head; //找到对应的类
}
else
{
return Tail::CreatObj(pName );//这里就是对Typelist进行了递归调用,从而产生了分支代码
}
}
};
template
struct Typelist//特化用以递归结束条件
{
static void* CreatObj(const char *pName)
{
if (strcmp(Head::m_classname, pName) == 0 )
{
return new Head;
}
else
{
return NULL;
}
}
};
ClassA* pa = (ClassA* )mytypelist:: CreatObj(“ClassA”);
ClassB* pb = (ClassB* )mytypelist:: CreatObj(“ClassB”);
ClassC* pc = (ClassC* )mytypelist:: CreatObj(“ClassC”);
…
template
struct Typelist
{
typedef T Head;
typedef U Tail;
template
static bool IsKindOf(const char *pName)
{
if (strcmp(Head:: getClassName(), pName) == 0 )
{
return Conversion::exists;
}
else
{
return Tail::IsKindOf(pName );
}
}
};
class ClassA;
class ClassB : public Class A;
class ClassC;
ClassA* pa = new ClassA;
ClassB* pb = new ClassB;
typedef Typelist> >
mytypelist ;
printf(“%d, %d, %d,%d”, mytypelist::IsKindOf< ClassA >(pa->getClassName()),mytypelist::IsKindOf< ClassB >(pa->getClassName()),
mytypelist::IsKindOf< ClassC >(pa->getClassName()),
mytypelist::IsKindOf< ClassA >(pb->getClassName()));
// 结果是 1,0,0,1