首页 > 代码库 > C++ 模板应用浅析
C++ 模板应用浅析
我不打算更深入地学习C++了,想最后把以前写的C++模板的应用心得发表出来。回想起当时在学习C++模板时的无助和恐惧,现在还心有余悸,其实我现在都还是一样地的有畏惧,当看到模板套模板,不停地typedef,这样的变态代码的时候,就觉得很扯蛋,编程不是让人快乐的吗?这样痛苦是为了什么,写这样难看的东西是为了什么。有更好的动态语言为什么不用?我也想不明白,我也不想争论哪个语言更好。但我分享出来我的心得,只希望别人少走弯路,模板就是特别装逼的,其实就这么几种用法,没啥。
我总结了模板的四种使用方式,基本覆盖了大部分的模板使用场景,了解了这四种方式,就可以在看其它代码时理解别人为什么会在这个地方用模板。
模板的四大场景
1.数据类型与算法相分离的泛型编程
2.类型适配Traits
3.函数转发
4.元编程
1.数据类型与算法相分离的泛型编程
如:
std::vector<int> std::vector<long>
如:
template <class T> class Singleton { protected: Singleton(){} public: static T& GetInstance() { static T instance; return instance; } }; Class CMySingleton : public Singleton< CMySingleton >
数据类型与算法的分离是最容易理解的一种使用场景,我觉得这可能也是发明泛型算法的初衷。
2.类型适配Traits
class A1 { public: void fun(); }; class A2 { public: void fun(); }; template<typename A> class CFunInvoker { public: Static void invoke(A* t) { t->fun(); } } A1 a1; A2 a2; CFunInvoker<A1>::invoke(&a1); CFunInvoker<A2>::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<int>::invoke但int类型又提供不了一个名为fun参数为空的函数。
因此我们引入静多态的另一个用处:Traits(粹取)
比如下面这个Host类需要模板参数类型提供一个叫dosomething的方法,所以Host<A>是可以编译通过,但Host<int>是编译不过的
为了解决这个问题,我们增加一个Traits类,它一定会对外提供一个dosomething的方法。对于普通类型,它就转发这个方法,于对int型,它作了特化,实现了一个空的dosomething的方法。因此无论是Host<Traits<A>> 还是Host<Traits<int>>,都可以通过编译
STL中大量运用了traits。比如我们常见的string类型,别以为它只能处理字符串,它可以处理任何类型,你甚至可以用它来处理二进制的buffer(binaryarray)。
比如我们可以修改std::string让其内部处理long类型,让它成为一个long型数组。
typedef basic_string<long, char_traits<long>, allocator<long> > longstring; longstring strlong; strlong.push_back(23); strlong.push_back(4562); long arrLong[2] = {23, 4562}; longstring strlongFromArr(arrLong, ARRAYSIZE(arrLong)); assert(strlong == strlongFromArr);
3.函数转发
模板类的很多应用在于它能针对不同的模板参数生成不同的类。这使得我们可以通过模板类将函数指针以及它的参数类型记录下来,在需要的时候再对函数进行调用。
基于函数转发的应用有很多
- boost::function
- boost::signal slot
- 模板实现的C++委托
- 模板实现的C++反射
凡是涉及到把函数指针存放起来,进行延迟调用的情况,都可以应用函数转发
下面模拟一个简单的转发
template<typename T> class function; template<typename R, typename A0> class function <R (A0)> { 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<int (int)> f1(&testfun); f1(4);上面的例子把函数testfun的函数指针,以及它的函数签名int (int)作为模板参数保存在了f1这个对象中。在需要的时候,就可以用f1对这个函数进行调用。
下面的例子模拟了类成员函数的转发
<pre name="code" class="cpp">template<class T> class function; template<typename R, typename A0, typename T> class function<R (T::*)(A0) > { 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<void (CA::*)(int)> 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++委托的原理
4.元编程
prinft("%d",5050);
产生的汇编指令是一样的。
利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。
- 模板的特化
- 函数重载决议
- typedef
- static类型变量和函数
- sizeof,
- =,:?,-,+,<, >运算符
- enum
1。元编程中特化用法
包括普通if的判断
循环条件终结判断
。。。。。
struct is_void { enum{value = false;} } template<> struct is_void<void> { enum{value = true;} } std::cout << is_void<int> //显示false
上面这个例子可以用来判断一个类型是不是void类型
2。元编程中函数重载决议用法
下面这个例子来自于《C++设计模式新思维》<pre name="code" class="cpp">template <class T, class U> 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<B*, A*>::exists, Conversion<A*, B*>::exists); 输出1,0上面的例子通过重载决议和sizeof取得重载函数Test的返回值大小,再通过枚举常量exists在编译期保存。
Conversion<B*, A*>中,重载决议采用的是char Test(A*)方法,因此Conversion<B*,A*>::exists为1。
Conversion<A*, B*>中,重载决议采用的是long Test(...)方法,因此Conversion<A*,B*>::exists为0。
3。元编程中typedef用法
在元编程中,typedef主要用来形成编译期的类型数据结构。最经典的TypeList结构
boost::tuple结构也是基于类似TypeList的结构
Boost的mpl库中还实现了vector map set等数据结构
typedef struct NULL_TYPE{} NullType template<typename T, typename U = NullType> struct Typelist { typedef T Head; typedef U Tail; } typedef Typelist<ClassA, Typelist< ClassB, Typelist< ClassC, NullType>>> mytypelist ;
template<typename T, typename U = NullType> struct Typelist { typedef T Head; typedef U Tail; Head m_head; Tail m_tail; } Typelist<ClassA, Typelist <ClassB>> storage; Storage. m_head = ClassA(); Storage.m_tail.m_head = ClassB();
这样链表就存了ClassA和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<ClassA, Typelist< ClassB, Typelist< ClassC> > > mytypelist ; template<typename T, typename U> 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<typename T> struct Typelist<T, NullType >//特化用以递归结束条件 { 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”); …
1.动态类型创建
ClassA* pObj = CreateObj(“ClassA”);
2.动态类型识别
pObj ->IsKindOf( ClassA::GetRuntimeClass() ) ;
在MFC中,IsKindOf 方法是通过遍历继承链来确定是否属于某种类型。一看到这种遍历或循环的方式,我们就可以考虑用模块递归来实现
template<typename T, typename U = NullType> struct Typelist { typedef T Head; typedef U Tail; template<typename SuperClass> static bool IsKindOf(const char *pName) { if (strcmp(Head:: getClassName(), pName) == 0 ) { return Conversion<Head*, SuperClass*>::exists; } else { return Tail::IsKindOf<SuperClass>(pName ); } } }; class ClassA; class ClassB : public Class A; class ClassC; ClassA* pa = new ClassA; ClassB* pb = new ClassB; typedef Typelist<ClassA, Typelist< ClassB, Typelist< ClassC>> > 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
元编程技术很多,比如还有数值运算等(最简单的1到100累加的例子),我这里只是挂一漏万,具体可以参考《C++设计模式新思维》。
C++ 模板应用浅析