以boost::function和boost:bind取代虛函數

基本用途編程


boost::function就像C#裏的delegate,能夠指向任何函數,包括成員函數。當用bind把某個成員函數綁到某個對象上時,咱們獲得了一個closure(閉包)。例如:設計模式

class Foo

{

 public:

  void methodA();

  void methodInt(int a);

};

class Bar

{

 public:

  void methodB();

};


boost::function<void()> f1; // 無參數,無返回值


Foo foo;

f1 = boost::bind(&Foo::methodA, &foo);

f1(); // 調用 foo.methodA();

Bar bar;

f1 = boost::bind(&Bar::methodB, &bar);

f1(); // 調用 bar.methodB();


f1 = boost::bind(&Foo::methodInt, &foo, 42);

f1(); // 調用 foo.methodInt(42);


boost::function<void(int)> f2; // int 參數,無返回值

f2 = boost::bind(&Foo::methodInt, &foo, _1);

f2(53); // 調用 foo.methodInt(53);

若是沒有boost::bind,那麼boost::function就什麼都不是,而有了bind(),「同一個類的不一樣對象能夠delegate給不一樣的實現,從而實現不一樣的行爲」(myan語),簡直就無敵了。閉包


對程序庫的影響編程語言


程序庫的設計不該該給使用者帶來沒必要要的限制(耦合),而繼承是僅次於最強的一種耦合(最強耦合的是友元)。若是一個程序庫限制其使用者必須從某個class派生,那麼我以爲這是一個糟糕的設計。不巧的是,目前有些程序庫就是這麼作的。函數


例1:線程庫工具


常規OO設計: 線程


寫一個Thread base class,含有(純)虛函數 Thread#run(),而後應用程序派生一個繼承class,覆寫run()。程序裏的每一種線程對應一個Thread的派生類。例如Java的Thread能夠這麼用。設計

 

缺點:若是一個class的三個method須要在三個不一樣的線程中執行,就得寫helper class(es)並玩一些OO把戲。code


基於closure的設計: 面向對象設計模式


令Thread是一個具體類,其構造函數接受Callable對象。應用程序只需提供一個Callable對象,建立一份Thread實體,調用Thread#start()便可。Java的Thread也能夠這麼用,傳入一個Runnable對象。C#的Thread只支持這一種用法,構造函數的參數是delegate ThreadStart。boost::thread也只支持這種用法。

// 一個基於 closure 的 Thread class 基本結構

class Thread 

{ 

 public: 

  typedef boost::function<void()> ThreadCallback; 

  Thread(ThreadCallback cb) : cb_(cb) 

  { } 

  void start() 

  { 

    /* some magic to call run() in new created thread */ 

  } 

 private: 

  void run() 

  { 

    cb_(); 

  } 

  ThreadCallback cb_; 

  // ... 

}; 


使用:

class Foo

{

 public:

  void runInThread();

};


Foo foo;

Thread thread(boost::bind(&Foo::runInThread, &foo));

thread.start();

對面向對象程序設計的影響


一直以來,我對面向對象有一種厭惡感,疊牀架屋,繞來繞去的,一拳拳打在棉花上,不解決實際問題。面向對象三要素是封裝、繼承和多態。我認爲封裝是根本的,繼承和多態則是無關緊要。用class來表示concept,這是根本的;至於繼承和多態,其耦合性太強,每每不划算。

繼承和多態不只規定了函數的名稱、參數、返回類型,還規定了類的繼承關係。在現代的OO編程語言裏,藉助反射和attribute/annotation,已經大大放寬了限制。舉例來講,JUnit 3.x 是用反射,找出派生類裏的名字符合 void test*() 的函數來執行,這裏就沒繼承什麼事,只是對函數的名稱有部分限制(繼承是全面限制,一字不差)。至於JUnit 4.x 和 NUnit 2.x 則更進一步,以annoatation/attribute來標明test case,更沒繼承什麼事了。

個人猜想是,當初提出面向對象的時候,closure尚未一個通用的實現,因此它沒能算做基本的抽象工具之一。如今既然closure已經這麼方便了,或許咱們應該從新審視面向對象設計,至少不要那麼濫用繼承。

自從找到了boost::function+boost::bind這對神兵利器,不用再考慮類直接的繼承關係,只須要基於對象的設計(object-based),拳拳到肉,程序寫起來頓時順手了不少。


對面向對象設計模式的影響


既然虛函數能用closure代替,那麼不少OO設計模式,尤爲是行爲模式,失去了存在的必要。另外,既然沒有繼承體系,那麼建立型模式彷佛也沒啥用了。

最明顯的是Strategy,不用累贅的Strategy基類和ConcreteStrategyA、ConcreteStrategyB等派生類,一個boost::function<>成員就解決問題。在《設計模式》這本書提到了23個模式,我認爲iterator有用(或許再加個State),其餘都在擺譜,拉虛架子,沒啥用。或許它們解決了面向對象中的常見問題,不過要是個人程序裏連面向對象(指繼承和多態)都不用,那彷佛也不用叨擾面向對象設計模式了。

或許closure-based programming將做爲一種新的programming paradiam而流行起來。

相關文章
相關標籤/搜索