記錄下boost::bind的一個例子

Boost.Bind 爲函數和函數對象提供了一致的語法,對於值語義和指針語義也一樣。我們將從一些簡單的例子開始,處理一些簡單綁定的用法,然後再轉移到通過嵌套綁定進行函數組合。弄明白如何使用 bind 的關鍵是,佔位符的概念。佔位符用於表示提供給結果函數對象的參數,Boost.Bind 支持最多九個參數。佔位符被命名爲 _1, _2, _3, _4, 直至 _9, 你要把它們放在你原先放參數的地方。作爲第一個例子,我們定義一個函數,nine_arguments, 它將被一個 bind 表達式調用。

#include <iostream>
#include "boost/bind.hpp"

void nine_arguments(
  int i1,int i2,int i3,int i4,
  int i5,int i6,int i7,int i8, int i9) {
  std::cout << i1 << i2 << i3 << i4 << i5
    << i6 << i7 << i8 << i9 << '\n';
}

int main() {
  int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9;
  (boost::bind(&nine_arguments,_9,_2,_1,_6,_3,_8,_4,_5,_7))
    (i1,i2,i3,i4,i5,i6,i7,i8,i9);
}

在這個例子中,你創建了一個匿名臨時綁定器,並立即把參數傳遞給它的調用操作符來調用它。如你所見,佔位符的順序是被攪亂的,這說明參數的順序被重新安排了。注意,佔位符可以在一個表達式中被多次使用。這個程序的輸出如下。

921638457
這表示了佔位符對應於它的數字所示位置的參數,即 _1 被第一個參數替換,_2 被第二個參數替換,等等。接下來,你將看到如何調用一個類的成員函數。

調用成員函數
我們來看一下如何用 bind 調用成員函數。我們先來做一些可以用標準庫來做的事情,這樣可以對比一下用 Boost.Bind 的方法。保存某種類型的元素在一個標準庫容器中,一個常見的需要是對某些或全部元素調用一個成員函數。這可以用一個循環來完成,通常也正是這樣做的,但還有更好的方法。考慮下面這個簡單的類,status, 我們將用它來示範 Boost.Bind 的易用性和強大的功能。

class status {
  std::string name_;
  bool ok_;
public:
  status(const std::string& name):name_(name),ok_(true) {}

  void break_it() {
    ok_=false;
  }

  bool is_broken() const {
    return ok_;
  }

  void report() const {
    std::cout << name_ << " is " <<
      (ok_ ? "working nominally":"terribly broken") << '\n';
  }
};

如果我們把這個類的實例保存在一個 vector, 並且我們需要調用成員函數 report, 我們可能會象下面這樣做。

std::vector<status> statuses;
statuses.push_back(status("status 1"));
statuses.push_back(status("status 2"));
statuses.push_back(status("status 3"));
statuses.push_back(status("status 4"));

statuses[1].break_it();
statuses[2].break_it();

for (std::vector<status>::iterator it=statuses.begin();
  it!=statuses.end();++it) {
  it->report();
}

這個循環正確地完成了任務,但它是冗長、低效的(由於要多次調用 statuses.end()),並且不象使用標準庫算法 for_each 那樣清楚地表明意圖。爲了用 for_each 來替換這個循環,我們需要用一個適配器來對 vector 元素調用成員函數 report 。這時,由於元素是以值的方式保存的,我們需要的是適配器 mem_fun_ref.

std::for_each(
  statuses.begin(),
  statuses.end(),
  std::mem_fun_ref(&status::report));

這是一個正確、合理的方法,它非常簡潔,非常清楚這段代碼是幹什麼的。以下是使用Boost.Bind 完成相同任務的代碼。[1]

[1] 要注意的是boost::mem_fn, 它也被接納進入Library Technical Report, 它也可以在這種沒有參數的情況下使用。 mem_fn 取代了 std::mem_fun 和 std::mem_fun_ref.

std::for_each(
  statuses.begin(),
  statuses.end(),
  boost::bind(&status::report,_1));

這個版本同樣的清楚、明白。這是前面所說的佔位符的第一個真正的使用,我們同時告訴編譯器和代碼的讀者,_1 用於替換這個函數所調用的綁定器的第一個實際參數。雖然這段代碼節省了幾個字符,但在這種情況下標準庫的 mem_fun_ref 和 bind 之間並沒有太大的不同,但是讓我們來重用這個例子並把容器改爲存儲指針。

std::vector<status*> p_statuses;
p_statuses.push_back(new status("status 1"));
p_statuses.push_back(new status("status 2"));
p_statuses.push_back(new status("status 3"));
p_statuses.push_back(new status("status 4"));

p_statuses[1]->break_it();
p_statuses[2]->break_it();

我們還可以使用標準庫,但不能再用 mem_fun_ref. 我們需要的是適配器 mem_fun, 它被認爲有點用詞不當,但它的確正確完成了需要做的工作。

std::for_each(
  p_statuses.begin(),
  p_statuses.end(),
  std::mem_fun(&status::report));

雖然這也可以工作,但語法變了,即使我們想做的事情非常相似。如果語法可以與第一個例子相同,那就更好了,所以我們所關心的是代碼要做什麼,而不是如何去做。使用bind, 我們就無須關心我們處理的元素是指針了(這一點已經在容器類型的聲明中表明瞭,對於現代的庫來說,這樣的冗餘信息是不需要的)。

std::for_each(
  p_statuses.begin(),
  p_statuses.end(),
  boost::bind(&status::report,_1));

如你所見,這與我們前一個例子完全一樣,這意味着如果我們之前已經明白了 bind ,那麼我們現在也清楚它。現在,我們已決定換用指針了,我們要面對另一個問題,即生存期控制。我們必須手工釋放 p_statuses 中的元素,這很容易出錯,也無須如此。所以,我們可能決定開始使用智能指針,並(再次)修改我們的代碼。

std::vector<boost::shared_ptr<status> > s_statuses;
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 1")));
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 2")));
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 3")));
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 4")));
s_statuses[1]->break_it();
s_statuses[2]->break_it();

現在,我們要用標準庫中的哪個適配器呢?mem_fun 和 mem_fun_ref 都不適用,因爲智能指針沒有一個名爲 report 的成員函數,所以以下代碼編譯失敗。

std::for_each(
  s_statuses.begin(),
  s_statuses.end(),
  std::mem_fun(&status::report));

不巧,標準庫不能幫我們完成這個任務[2]。因此,我們不得不採用我們正想要擺脫的循環,或者使用 Boost.Bind, 它不會抱怨任何事情,而且正確地完成我們想要的。

[2] 以後將可以這樣做,因爲 mem_fn 和 bind 都將成爲未來的標準庫的一部分。

std::for_each(
  s_statuses.begin(),
  s_statuses.end(),
  boost::bind(&status::report,_1));

再一次,這段代碼與前面的例子完全一樣(除了容器的名字不同)。使用綁定的語法是一致的,不論是用於值語義或是指針語義,甚至是用於智能指針。有時,使用不同的語法有助於理解代碼,但在這裏,不是這樣的,我們的任務是對容器中的元素調用成員函數,沒有更多的也沒有更少的事情。語法一致的價值不應被低估,因爲它對於編寫代碼的人,以及對於日後需要維護代碼的人都是有幫助的(當然,我們並不真的是在寫需要維護的代碼,但爲了這個主題,讓我們假裝是在寫)。

這些例子示範了一個非常基本和常見的情形,在這種情形下 Boost.Bind 尤爲出色。即使標準庫也提供了完成相同工作的一些基本工具,但我們還是看到 Bind 既提供了一致的語法,也增加了標準庫目前缺少的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章