C++ 模板应用浅析
把曾经写的C++模板的应用心得发表出来。
回忆起当时在学习C++模板时的无助和恐惧,如今还心有余悸。我分享出来我的心得,仅仅希望别人少走弯路,事实上它就这么几种使用方法,不须要害怕。
我总结了模板的四种使用方式,基本覆盖了大部分的模板使用场景,了解了这四种方式。就能够在看其他代码时理解别人为什么会在这个地方用模板。
模板的四大场景
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 >
数据类型与算法的分离是最easy理解的一种使用场景。我认为这可能也是发明泛型算法的初衷。
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>是编译只是的
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3l4aXNncmVhdA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" style="font-size:18px">
为了解决问题。我们添加一个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对这个对象的这个成员函数函数进行调用。
调用的方式非常easy
f(4); // 等价于a.Fun(4);
就像是调用一个普通的C函数一样。CA类对象不见了,.或->操作符不见了。函数转发实现了一层层的封装与绑定。终于上调用者与CA类型隔离,实现了解耦。
只是函数转发的这样的封装使会使得调用效率减少。怎样让封装后的调用像普通函数调用一样快,请參考我发的还有一篇学习心得
4.元编程
从一个演示样例入手:一段从1累加到100的程序
然后为N=1的时候特化处理value=1。
这样在GetSum<100>这个类中它的value值就是5050。这个值不是在执行时候计算机算的,而是在编译时编译器已经算好了。这么长的C++代码终于编译出来的结果就和仅仅写一句
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++ 模板应用浅析的更多相关文章
- Handlebars模板库浅析
Handlebars模板库简单介绍 Handlebars是JavaScript一个语义模板库,通过对view(模板)和data(ajax请求的数据,一般是json)的分离来快速构建Web模板.它采用& ...
- 浅析js模板引擎
js模板引擎越来越多的得到应用,如今已经出现了几十种js模板引擎,国内各大互联网公司也都开发了自己的js模板引擎(淘宝的kissy template,腾讯的artTemplate,百度的baiduTe ...
- [模板]ST表浅析
ST表,稀疏表,用于求解经典的RMQ问题.即区间最值问题. Problem: 给定n个数和q个询问,对于给定的每个询问有l,r,求区间[l,r]的最大值.. Solution: 主要思想是倍增和区间d ...
- trie字典树模板浅析
什么是trie? 百度百科 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的 ...
- 各种JS模板引擎对比数据(高性能JavaScript模板引擎)
最近做了JS模板引擎测试,拿各个JS模板引擎在不同浏览器上去运行同一程序,下面是模板引擎测试数据:通过测试artTemplate.juicer与doT引擎模板整体性能要有绝对优势: js模板引擎 Ja ...
- [转载]C++虚函数浅析
原文:http://glgjing.github.io/blog/2015/01/03/c-plus-plus-xu-han-shu-qian-xi/ 感谢:单刀土豆 C++虚函数浅析 JAN 3RD ...
- typecho流程原理和插件机制浅析(第一弹)
typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...
- 转载:JMS-ActiveMQ浅析
ActiveMQ 即时通讯服务 浅析 一. 概述与介绍 ActiveMQ 是Apache出品,最流行的.功能强大的即时通讯和集成模式的开源服务器.ActiveMQ 是一个完全支持JMS1.1和J2EE ...
- Android开发之Theme、Style探索及源码浅析
1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...
随机推荐
- Delphi第三方控件安装卸载指南
基本安装1.对于单个控件,Componet-->install component..-->PAS或DCU文件-->install; 2.对于带*.dpk文件的控件包,File--& ...
- C#Windows服务安装
1,做好windows服务后,生成 一下,然后在项目目录中找到bin文件夹下的Debug文件夹,文件夹下有文件xxxx.exe 2,然后在C:\Windows\Microsoft.NET\Framew ...
- mysql条件查询and or使用实例及优先级介绍
mysql and与or介绍 AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来. 使用OR关键字时: 只要符合这几个查询条件的其中一个条件,这样的记录就会被查询出来. 如果不符合这 ...
- ubuntu14.04 software-center can not open
sudo apt-get update sudo apt-get dist-upgrade sudo apt-get install --reinstall software-center
- 刷题总结——spoj1812(后缀自动机+DP)
题目: A string is finite sequence of characters over a non-empty finite set Σ. In this problem, Σ is t ...
- 【DFS求树的最大二分匹配+输入外挂】HDU 6178 Monkeys
http://acm.hdu.edu.cn/showproblem.php?pid=6178 [题意] 给定一棵有n个结点的树,现在有k个猴子分布在k个结点上,我们可以删去树上的一些边,使得k个猴子每 ...
- 森林 BZOJ 3123
题解: 第k大直接用主席树解决 合并利用启发式合并,将较小的连接到较大的树上 #include<cmath> #include<cstdio> #include<cstd ...
- PatentTips - Optimizing Write Combining Performance
BACKGROUND OF THE INVENTION The use of a cache memory with a processor facilitates the reduction of ...
- PHP提示Cannot modify header information - headers already sent by解决方法
PHP提示Cannot modify header information - headers already sent by解决方法 因为 header();发送头之前不能有任何输出,空格也不行, ...
- android的系统学习
先从Android的应用开发开始,等到对应用掌握的比较熟悉了,开始慢慢阅读一些Android 应用框架层的源代码,然后再渐渐往下去了解Android的JNI.Libraries.Dalvik虚拟机.H ...