Curiously recurring template pattern 与C++的静态多态
引言
最近在找C++的数值库,最后终于找到一个近乎完美的eigen,接口优雅,使用方便,速度快,文档详尽。。。 几乎没有缺点,缺点可能是库的设计者要辛苦一些。。。。另外稀疏矩阵的部分还在开发阶段没有完全成熟。
里面提到了矩阵相乘相加等等可以直接用 D =A * B *C 这样写,而不是类似boost::ublas(这个库的好处是在boost下,其它的比eigen差远了。。个人觉得)要写成 prod(A,B)。
为什么ublas不用运算符号重载呢? 原因是效率问题吧,当然A*B的写法更优雅更类似matlab语法了,那么在eigen中采用了lazy evaluation来避免效率问题。
考虑两个向量相加
u = v + w; // (*)
egigen中使得它能编译为类似下面的代码
for(int i = 0; i < size; i++) u[i] = v[i] + w[i];
如果采用简单运算符重载那么其实类似下面的代码
VectorXf tmp = v + w; VectorXf u = tmp;
也就是类似下面这样
for(int i = 0; i < size; i++) tmp[i] = v[i] + w[i]; for(int i = 0; i < size; i++) u[i] = tmp[i];
效率问题就是
首先我们需要动态开辟tmp空间
然后我们需要赋值一次 也就是 本来访问size次完成的事情 要2*size次
C++0X推出了了右值引用,上面第二个问题可以解决,类似只要多加一次指针拷贝std::move,但是呢如果u是提前开辟好空间的,你还是要多一次tmp空间的开辟时间,另外很多公司编译器恐怕都还不支持右值引用(需要gcc4.5)
lazy evaluation怎么解决了这个问题呢,看eigen的文档吧http://eigen.tuxfamily.org/dox-devel/TopicInsideEigenExample.html 。
动态多态应用,常用的一个设计模式template method pattern
考虑我们写分词器,我们可能考虑写几个不同的分词器,但是主的切分流程segment是相同的,具体的内部实现部分可能某些部件对于不同的分词器有不同实现,那么我提取一个基类SegBase实现这个切分流程,
提供一个部件的默认实现,而不同的分词器继承SegBase从而继承这个相同的切分流程,但是可以改写部件的实现。
#include <iostream> using namespace std; class SegBase { public: //流程框架 void segment() { for (int i = 0; i < 2; i++) { deal(); //流程内的某个核心部件函数 } } virtual void deal() { cout << "In segBase" << endl; } }; class Seg : public SegBase { public: virtual void deal() { cout << "In Seg" << endl; } }; void run() { Seg seg; seg.segment(); } int main(int argc, char *argv[]) { run(); return 0; }
结果是:
In Seg
In Seg
这里的问题是我们需要对deal采用虚函数,而deal是很核心的代码可能在框架流程中大量调用,我们对于虚函数带来的效率损失可能是在意的。。。。 如果我们把segment()改为虚函数,改deal为普通函数,虽然避免效率损失达不到效果了就,
如下
#include <iostream> using namespace std; class SegBase { public: //流程框架 virtual void segment() { for (int i = 0; i < 2; i++) { deal(); //流程内的某个核心部件函数 } } void deal() { cout << "In segBase" << endl; } }; class Seg : public SegBase { public: void deal() { cout << "In Seg" << endl; } }; void run() { Seg seg; seg.segment(); } int main(int argc, char *argv[]) { run(); return 0; }
结果是
In segBase
In segBase
使用CRTP方法来达到静态多态
CRTP模式基本代码架构就是
// The Curiously Recurring Template Pattern (CRTP) template <typename T> struct base { // ... }; struct derived : base<derived> { // ... };
我们考虑用CRTP方法来完成我们上面提到的分词器代码设计并且避免使用虚函数以规避掉效率损失问题。
#include <iostream> using namespace std; template<typename Derived> class SegBase { public: //流程框架 void segment() { for (int i = 0; i < 2; i++) { deal(); //流程内的某个核心部件函数 } } void deal_default() { cout << "Use SegBase default" << endl; } void deal() { static_cast<Derived*> (this)->deal(); } }; class Seg : public SegBase<Seg> { public: void deal() { //Seg改写deal函数 cout << "Use Seg modified " << endl; } }; class Seg2 : public SegBase<Seg2> { public: typedef SegBase<Seg2> Base; void deal() { //Seg2复用SegBase的默认deal函数 Base::deal_default(); } }; void run() { Seg seg; seg.segment(); Seg2 seg2; seg2.segment(); } int main(int argc, char *argv[]) { run(); return 0; }
运行结果:
Use Seg modified
Use Seg modified
Use SegBase default
Use SegBase default
从库的设计实现角度来讲不算太优雅不如虚函数的方案优雅,因为所有考虑到需要子类重新的函数,都要手工写上转交代码类似static_cast<Derived*> (this)->deal(); ,库作者写起来要比用虚函数麻烦些, 但是效果还不错,对使用者来讲接口是一样的实现是透明的,速度的损失也没有了,不失为一种解决方案~:),很多C++模板库其实都是大量采用这种方案的比如eigen,openmesh等等。
注意静态多态还有其它可能的实现方法,比如deal单独提出到一个类里面,作为模板参数,类似这样实现静态多态,但是那样设计层次就更多更复杂了, 当然 crtp还有其它的用处了,参考
http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern