引言
最近在找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];
效率問題就是
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