新的bind庫 (bi-bind), 對boost::bind的精簡

boost中有一個bind庫, 能夠說是一個最爲實用的tools了, 可是它與boost結合的有些緊密,並且其中的一些功能並非很經常使用,就算將它bcp出獨立的庫也是一個不小的負擔。若是在你的項目中不打算有boost庫的痕跡可是又想使用bind的強大功能,那就來看看它吧。


一個一個超小型的bind庫, 它實現了大部分boost::bind的功能, 只是將名字空間由boost 變換爲 bi 。若是使用了通常的使用中一般能夠將 html

boost::bind(my_fun(), _1,_2)(234, "hello world");

//形式替換爲 

bi::bind(my_fun(), _1, _2)(234, "hello world");


既可完成編譯,若是使用了名字空間,那就只須要將 using namespace boost 替換爲 using namespafce bi 便可完成轉化。


同boost::bind同樣,它支持任意的函數對象,函數,函數指針,和成員函數指針,它還能將任何參數綁定爲一個特定的值,或者將輸入的參數發送到任意的位置。


從使用者方向看只是將bind 從boost名字空間移動到了 bi名字空間, 若是使用了 using namespace boost;的方式引入的boost::bind, 那麼你很幸運!只要改爲using namespace bi;就完成了。


同函數及函數指針一塊兒使用 bind


給定一些函數定義:

int f(int a, int b)
{
  return a + b;
}

int g(int a, int b, int c)
{
  return a + b + c;
}
bind(f, 1, 2) 會產生一個函數對象,它將f(1, 2)這樣的函數調用包裝到了自身,一樣,bind(g, 1, 2, 3)() 的形式調用等價於 g(1, 2, 3)。


並且只綁定一部分參數也是有可能的。bind(f, 1, 5)(x) 等價於 f(x, 5),這裏,_1 是一個佔位符參數,它的含義是「用第一個輸入參數取代」。


這個實現支持最多 9 個參數的函數對象,同時也只提供了最大一9的佔位符 _9。這只是一個實現細節,不是設計的固有限制,從理論上說若是有須要能夠提供任意多個參數的函數調用。


bind 可以處理帶有兩個以上參數的函數,並且它的參數取代機制更爲直觀:
bind(f, _2, _1)(x, y); // f(y, x)

bind(g, _1, 9, _1)(x); // g(x, 9, x)

bind(g, _3, _3, _3)(x, y, z); // g(z, z, z)

bind(g, _1, _1, _1)(x, y, z); // g(x, x, x)
注意,最後一個示例中,由 bind(g, _1, _1, _1) 生成的函數對象不包含對第一個參數之外的任何參數的引用,可是它仍然能使用一個以上的參數。全部多餘的參數被悄悄地忽略,就像在第三個示例中,第一和第二個參數被忽略。


bind 返回的函數對像同標準庫其它組件同樣具備值語義,因此它持有輸入參數,並內部持有函數對象拷貝。例如,在下面的代碼中:
int i = 5;

bind(f, i, _1);
一個 i 的值的拷貝被存儲於bind生成的函數對象中。


在表達式 bind(f, a1, a2, ..., aN) 中,函數對象 f 必須可以持有正好 N 個參數。這個錯誤一般在「綁定時間」被查出。換句話說,這個編譯錯誤會被報告在 bind() 被調用的那一行:
int f(int, int);

int main()
{

  bi::bind(f, 1); // error, f函數f必須具有兩個參數

  bi::bind(f, 1, 2); // OK
}
這個錯誤的一個常見變化是忘記成員函數有一個隱含的 "this" 參數:



struct X
{

  int f(int);
};

int main()
{

  bi::bind(&X::f, 1); // error,函數f必須具有兩個參數

  bi::bind(&X::f, _1, 1); // OK
}
和函數對象一塊兒使用 bind


bind 並不限於函數,它能夠接受任何函數對象。一般狀況下,生成的函數對象的 operator() 的返回類型必須顯式指定(沒有 typeof 操做符,返回類型沒法推導):
struct F
{

  int operator()(int a, int b) { return a - b; }

  bool operator()(long a, long b) { return a == b; }
};

F f;

int x = 104;

bi::bind(bi::type<int>(), f, _1, _1)(x);
bi::bind庫不支持 bind<int>(f, _1, _1)(x); 形式的調用, boost支持這種調用方式。


當函數對象暴露了一個名爲 result_type 的內嵌類型時,顯式返回類型能夠被省略:
int x = 8;

bind(std::less<int>(), _1, 9)(x); // x < 9
bind(f, ...) 和 bind<R>(f, ...) 有什麼不一樣


第一個形式指示 bind 去檢查 f 的類型以肯定它的 arity(參數數量)和返回類型。參數數量錯誤將在「綁定時間」查明。固然,這樣的語法對 f 有必定的要求。它必須是一個函數,函數指針,成員函數指針,或定義了一個內嵌的名爲 result_type 的類型的函數對象,簡而言之,它必須是某種 bind 能夠識別的東西。


第二個形式指示 bind 不要試圖識別 f 的類型。它一般和那些沒有或不能暴露 result_type 的函數對象一塊兒使用,可是它也能和非標準函數一塊兒使用。例如,當前實現不能自動識別像 printf 這樣的可變參數函數,因此你必須用 bind<int>(printf, ...)。注意,有一種可選的 bind(type<R>(), f, ...) 語法由於可移植性的緣由也被支持。


另外一個須要考慮的重要因素是:當 f 是一個函數對象時,不支持模板偏特化或函數模板部分排序的編譯器不能處理第一種形式,並且,大部分狀況下,當 f 是一個函數(指針)或成員函數指針時,不能處理第二種形式。


和成員指針一塊兒使用 bind


成員函數的指針和數據成員的指針不是函數對象,由於它們不支持 operator()。爲了方便起見,bind 接受成員指針做爲它的第一個參數,而它的行爲就像使用 std::mem_fn 將成員指針轉換成一個函數對象同樣。同時bind天然支持boost::shared_ptr 對像的指針,並且不須要應引boost庫。


bind(&X::f, _args)


示例:
struct X
{

  bool f(int a);
};

X x;

Boost::shared_ptr<X> p(new X); //天然支持 boost::shared_ptr

int i = 5;

bind(&X::f, x, 1)(i); // ( _internal copy of x).f(i)

bind(&X::f, p, 1)(i); // ( _internal copy of p)->f(i)
最後兩個示例的有趣之處在於它們生成「自包含」的函數對象。bind(&X::f, x, _1) 存儲 x 的一個拷貝。bind(&X::f, p, _1) 存儲 p 的一個拷貝,並且由於 p 是一個 boost::shared_ptr,這個函數對象保存一個屬於它本身的 X 的實例的引用,並且當 p 離開它的做用域或者被 reset() 以後,這個引用依然保持有效。


綁定一個被重載的函數的企圖一般對致使一個錯誤,由於沒法表示到底要綁定哪個重載版本。對於帶有 const 和非 const 兩個重載的成員函數來講,這是一個很常見的問題,就像這個簡化的示例:
struct X

{

int& get();

  int const& get() const;

};

int main()

{

  bi::bind( &X::get, _1 );

}
這裏的二義性能夠經過將(成員)函數指針強制轉換到想要的類型來解決:



int main()
{

  bi::bind( static_cast< int const& (X::*) () const >( &X::get ), _1 );

}
另外一個或許更可讀的辦法是引入一個臨時變量:



int main()
{

  int const& (X::*get) () const = &X::get;

  bi::bind( get, _1 );

}
爲函數組合使用嵌套的 binds


傳給 bind 的某些參數能夠嵌套 bind 表達式自身:
bind(f, bind(g, _1))(x); // f(g(x))
當函數對象被調用的時候,若是沒有指定順序,內部 bind 表達式先於外部 bind 表達式被求值,在外部 bind 表達式被求值的時候,用內部表達式的求值結果取代它們的佔位符的位置。在上面的示例中,當用參數列表 (x) 調用那個函數對象的時候,bind(g, _1)(x) 首先被求值,生成 g(x),而後 bind(f, g(x))(x) 被求值,生成最終結果 f(g(x))。


bind 的這個特性可被用來執行函數組合。


注意第一個參數——被綁定函數對象——是不被求值的,即便它是一個由 bind 生成的函數對象或一個佔位符參數,因此下面的示例不會如你所願地工做:



typedef void (pf)(int);

std::vector<pf> v;

std::for_each(v.begin(), v.end(), bind(_1, 5));
儘管在缺省狀況下,第一個參數是不被求值的,而全部其它參數被求值。但有時候不須要對第一個參數以後的其它參數求值,甚至當它們是內嵌 *bind 子表達式的時候也不須要。


示例


和標準算法一塊兒使用 bind c++

class image;

class animation

{

public:

   void advance(int ms);

   bool inactive() const;

   void render(image & target) const;

};

std::vector<animation> anims;

template<class C, class P> void erase_if(C & c, P pred)

{

   c.erase(std::remove_if(c.begin(), c.end(), pred), c.end());

}

void update(int ms)

{

   std::for_each(anims.begin(), anims.end(),     ni::bind(&animation::advance, _1, ms));

  erase_if(anims, bi::mem_fn(&animation::inactive));

}

void render(image & target)

{

  std::for_each(anims.begin(), anims.end(), bi::bind(&animation::render, _1, bi::ref(target)));

}
侷限性


做爲一個通用規則,由 bind 生成的函數對象以引用方式持有它們的參數,所以,不能接受非 const 臨時對象或字面常量。


這個庫如下面這種形式的識別標識


template<class T> void f(T & t);


接受任意類型的參數並將它們不加改變地傳遞。注意,這不能用於非 const 右值。


在支持函數模板部分排序的編譯器上,一個可能的解決方案是增長一個重載:


template<class T> void f(T & t);


template<class T> void f(T const & t);


很不幸,對於 9 個參數,這樣須要提供 512 個重載,這是不切實際的,因此同boost::bind同樣,它只選擇實現了一個小的子集:對於不大於兩個參數的狀況,完整地提供了常量重載,對於三個及更多參數,它爲全部參數都以常引用方式持有的狀況提供了單一的補充重載。這覆蓋了使用狀況的一個合理的部分。




bi::bind沒有打算支持boost庫, 若是在你的項目中使用了其它的boost‘tools,那你就不須要它了(或許也能夠拿來研究研究),既然使用了boost,就不在意在使用boost::bind了,因此boost::bind中涉及到與其它boost庫的有交集的功能bi::bind庫都沒有支持。


它天然支持 boost::shared_ptr, 同時對c++10 保標準的shared_ptr也有很好的支持。


bi::bind借鑑了boost::bind的一些思想,有些代碼甚至是直接從bind.hpp中複製過來的,但它在犧牲一些擴展性和多平臺支持性的後果下, 一樣的代碼比boost::bind中有40%的效率提高 。


bi-bind 同時提供了 callback功能, 它就像是一個簡化版本的 boost::function, 對一次調用行爲作了抽象。 算法


int fun1(int p2)
{
   std::cout << "call fun1" << std::endl;
   return p2 *  p2;
}

struct fun2
{
   fun2(int p1)
   {
      p_ = p1;
   }

   int operator()(int p2) const
   {
      return p2 * p_;
   }

   int p_;
};
//綁定通常函數 構造 callback
bi::callback<int(int)> cb = bi::bind(&fun1, _1);
int r = cb(2);   //r=4,使用了實際參數2


//綁定仿函數對象賦值給callback
cb = bi::bind(fun2(2), 9);
r = cb(100);     //r=18,使用了綁定參數9
bind 一般都接收它們的參數的拷貝, 並會在bind_t對像中保存一份相同的對象,而有些對像不具有拷貝語義或是拷貝的代價很大。 爲了解決這個問題對,bind對像的可使用ref 方式將這個對像封裝起來使用。它定義了類模板 reference_wrapper<T>和 兩個返回實例的函數 ref 和 cref。


reference_wrapper<T> 的目的是容納一個引向類型爲 T 的對象的引用。它主要用於把引用傳給bind_t對像, bint_t對像中有對此的重載,便可以解開引用,調用實際的對像。


struct fun2
{
   fun2(int p1)
   {
      p_ = p1;
   }

   int operator()(int p2) const
   {
      return p2 * p_;
   }

   int p_;
};
//假設fun2的拷貝複製的負擔很大
fun2 f(23);


//使用bi::ref解除對f對像的值語義調用
bi::bind(bi::ref(f), 9)(2);
我沒有寫bi::bind的使用文檔, 由於它 在這裏 http://www.boost.org/doc/libs/1_49_0/libs/bind/bind.html。 庫地址: http://code.google.com/p/bi-bind/
相關文章
相關標籤/搜索