CRTP有什么用?
来源:互联网 发布:最新版广联达预算软件 编辑:程序博客网 时间:2024/09/21 08:52
参考 1.http://www.cnblogs.com/mightofcode/archive/2013/04/03/2996323.html
2.http://www.tuicool.com/articles/IfiAvq
什么是CRTP?
The curiously recurring template pattern (CRTP) is a C++ idiom in which a class X
derives from a class template instantiation using X
itself as template argument.
类X继承了一个以X作为模板参数的的模板,这就是CRTP,具体介绍请参看维基百科
CRTP简介
CRTP的意义是父类(接下来我们称之为CRTP父类,相应的子类成为CRTP子类)知道子类的类型,可以做一些虚函数做不到的事,比如维基百科里面提到的类计数,clone函数
这两个东西用虚函数做起来都不甚方便,本文的目的是探讨CRTP的通用场景
CRTP可以代替虚函数吗
不可以,虚函数实现了动态多态,也就是让基类指针指向子类,CRTP做不到这一点
根据维基,CRTP实现了"静态多态",关于"静态多态",我的理解是在编译时就根据基类指针转换为子类指针,达到类似多态的效果,但本质上跟动态多态还是两码事
所谓"静态多态"似乎不符合多态的定义:CRTP产生了不同的父类,所以不存在同样的基类指针指向不同子类这样的情况
或许"静态多态"应该换一个更直观的名字
那么CRTP有什么用呢?
在网上看了很多文章,大多语焉不详,举的例子大部分是类计数器,单例模式等看起来很trick,不实用的东西,不可否认这些东西确实"有用",但是你真的会用在在自己的项目中吗?
笔者以前写代码的时候也遇到过基类需要知道子类类型的情况,那个时候很自然的时候就用了CRTP,当时我并不知道这是CRTP,但是CRTP有没有普适性更强的用法呢?
包括荣毅的一篇文章http://wenku.baidu.com/view/a14844a1b0717fd5360cdcb2.html,也没有举实际的例子,看不懂
看看WTL
但是幸好有WTL,WTL中大量使用了CRTP,我们以CDoubleBufferImpl来看看WTL是如何运用CRTL的
CDoubleBufferImpl看名字猜测一个双缓冲的渲染封装,核心函数是OnPaint,DoPaint
CDoubleBufferImpl的定义:
template <class T>class CDoubleBufferImpl{public:// Overrideables void DoPaint(CDCHandle /*dc*/) //子类需要覆盖此函数,没有这个函数也行,但是会出现天书般的模板编译信息 { // must be implemented in a derived class ATLASSERT(FALSE); }// Message map and handlers BEGIN_MSG_MAP(CDoubleBufferImpl) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground) MESSAGE_HANDLER(WM_PAINT, OnPaint)#ifndef _WIN32_WCE MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)#endif // !_WIN32_WCE END_MSG_MAP() LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { return 1; // no background painting needed } LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { T* pT = static_cast<T*>(this); //典型CRTP代码,转换为子类指针 ATLASSERT(::IsWindow(pT->m_hWnd)); //1 if(wParam != NULL) { RECT rect = { 0 }; pT->GetClientRect(&rect); //2 CMemoryDC dcMem((HDC)wParam, rect); pT->DoPaint(dcMem.m_hDC); //3 } else { CPaintDC dc(pT->m_hWnd); //4 CMemoryDC dcMem(dc.m_hDC, dc.m_ps.rcPaint); pT->DoPaint(dcMem.m_hDC); //5 } return 0; }};
编号1,2,3,4,5表明子类需要实现的接口
实现一个双缓冲窗口的典型代码:
class TCtrl: public CWindowImpl< TCtrl>, public WTL::CDoubleBufferImpl<TCtrl> // 继承双缓冲类
这样TCtrl从CWindowimpl获得了窗口的行为,从CDoubleBufferWindowImpl获得了双缓冲的行为,从而得到了一个双缓冲窗口
MSG_MAP妨碍了我们的分析,我们抛开MSG_MAP,简化TCtrl的工作流程:
CWindowimpl接收到PAINT消息,这个消息又发给了CDoubleBufferImpl,CDoubleBufferImpl进行一些处理然后调用TCtrl的DoPaint完成绘制
在这里WTL使用多重继承+CRTP来拓展类的行为,而不是组合或者单继承
在这里使用CRTP有一个明显的好处:可以少写很多琐碎的代码,CDoubleBufferImpl模板知道子类的的类型,可以直接使用子类的接口
不用CRTP如何拓展类?
如果使用组合:
我们需要定义一个CDoubleBufferImpl类,这个类实现了双缓冲,注意它用到了GetClientRect之类的东西,所以我们的TCtrl需要把这些数据push到CDoubleBufferImpl,或者定义一些接口让CDoubleBufferImpl使用,
然后我们调用CDoubleBufferImpl类完成工作
如果使用单继承:
TCtrl需要重写一部分CWindowimpl的方法,在这些方法中实现双缓冲
组合的方法要写很多代码,虽然让CDoubleBufferImpl和CWindowimpl解耦,但是写这么多代码增加了很多复杂度
单继承的方法看起来非常不错似乎比多继承+CRTP还要简单,但是这样就把CWindowimpl和双缓冲的实现耦合起来了,如果我现在需要给CWindowimpl增加另一种特性,比如"ReSize",为了满足可变的需求,我们需要把库
设计得足够全面那么,我肯定需要把ReSize和双缓冲两个属性进行组合,这样就会产生4个类,而且会有重复代码(这个时候你肯定会想用组合来实现),如果我再想为添加另一种行为呢?结果是越来越多的类,代码很快就难以维护了
但是多继承+CRTP提供了另一种方式
使用多继承+CRTP比组合的代码少,比单继承易于拓展,要添加行为,只需要继承一个类就好了,多个行为相互组合?再继承几个
多继承+CRTP也有缺点:
1,使用模板,牵一发而动全身,改动模板会引起大量重编,做过大型c++项目的都明白这实在是一个难以忍受的过程,笔者所在的项目曾经有一个用得比较多的模板类(其实这个类完全没有必要使用模板),笔者有一段时间需要去改动
这个2000行的庞然大物,每一次改动都要编译几十分钟,苦不堪言
2,代码比较难读,特别是对于新手,这变相增加了维护成本
3,多继承+CRTP很灵活,但是封装性不如组合
总结:
1 CRTP可以用在任何基类需要子类类型的场合
2 多继承+CRTP提供了灵活构造类的方式
- CRTP有什么用?
- 人气有什么用?
- 系统分析有什么用
- 数学有什么用??
- AssociatedControlID有什么用?
- 数学有什么用?
- static有什么用?
- const 有什么用
- 看看有什么用?
- super() 有什么用?
- XML 有什么用
- 技术有什么用?
- 图层蒙版 有什么用
- SEO有什么用
- javascript有什么用?
- 蓝牙有什么用?
- 蓝牙有什么用?
- 技术有什么用?
- jquery阻止表单提交实例
- 动态增加行实例
- JAVA中的序列化
- IOS NSLog 打印bool值
- confirm弹框实例
- CRTP有什么用?
- jquery删除元素实例
- 源自梦想 自定义ViewGroup的整理_1
- Permutations -- LeetCode
- 动态增加元素时初始化jquery样式实例
- Linux设置JDK环境变量
- jquery的一些正则,亲测可用
- 源自梦想 自定义ViewGroup的整理_2
- jquery正则表达式