這次主要介紹的是函數對象,可能稱為“高階函數”更為適合。它實際上是指那些可以被傳入到其它函數或是從其它函數返回的一類函數,在C++中高階函數是被實現為函數對象的。本文將會介紹幾個用於處理函數對象的Boost C++庫。 其中,Boost.Bind可替換來自C++標準的著名的 std::bind1st() 和 std::bind2nd() 函數,而 Boost.Function則提供了一個用於封裝函數指標的類。 最後,Boost.Lambda則引入了一種建立匿名函數的方法。
一、Boost.Bind
Boost.Bind簡化了由C++標準中的std::bind1st和std::bind2nd 模板函數所提供的一個機制:將這些函數與幾乎不限數量的參數一起使用,就可以得到指定簽名的函數。這種情形的一個最好的例子就是在C++標準中定義的多個不同演算法。
#include <iostream> #include <vector> #include <algorithm> void print(int i) { std::cout << i << std::endl; } int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::for_each(v.begin(), v.end(), print); }
演算法std::for_each要求它的第三個參數是一個僅接受正好一個參數的函數或函數對象。如果std::for_each被執行,指定容器中的所有元素將按順序被傳入print函數。但是,如果要使用一個具有不同簽名的函數的話,事情就複雜了。如果要傳入的是以下函數add,它要將一個常數值加至容器中的每個元素上,並顯示結果。
void add(int i, int j) { std::cout << i + j << std::endl; }
由於std::for_each()要求的是僅接受一個參數的函數,所以不能直接傳入add()函數,必須要修改源碼。
#include <iostream> #include <vector> #include <algorithm> #include <functional> class add : public std::binary_function<int, int, void> { public: void operator()(int i, int j) const { std::cout << i + j << std::endl; } }; int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::for_each(v.begin(), v.end(), std::bind1st(add(), 10)); }
以上程式將值10加至容器v的每個元素之上,並使用標準輸出資料流顯示結果。原始碼必須作出大幅的修改,以實現此功能:add()函數已被轉換為一個派生自std::binary_function的函數對象。
Boost.Bind簡化了不同函數之間的綁定。它只包含一個boost::bind()模板函數,定義於boost/bind.hpp中。使用這個函數,可以如下實現以上例子:
#include <boost/bind.hpp> #include <iostream> #include <vector> #include <algorithm> void add(int i, int j) { std::cout << i + j << std::endl; } int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::for_each(v.begin(), v.end(), boost::bind(add, 10, _1)); }
象add這樣的函數不再需要為了要用於std::for_each而轉換為函數對象。使用 boost::bind,該函數可以忽略其第一個參數而使用。因為add函數要求兩個參數,兩個參數都必須傳遞給boost::bind。其中第一個參數是常數值10,而第二個參數則是一個比較奇怪的_1。
_1 被稱為預留位置(placeholder),定義於Boost.Bind。除了_1,Boost.Bind還定義了_2 和_3。通過使用這些預留位置,boost::bind可以變為一元、二元或三元的函數。對於_1, boost::bind變成了一個一元函數。這是必需的,因為 std::for_each正是要求一個一元函數作為其第三個參數。
當這個程式執行時,std::for_each對容器v中的第一個元素調用該一元函數。元素的值通過預留位置_1 傳入到一元函數中。這個預留位置和常數值被進一步傳遞到add函數。通過使用這種機制,std::for_each只看到了由boost::bind所定義的一元函數。而boost::bind本身則只是調用了另一個函數,並將常數值或預留位置作為參數傳入給它。
下面這個例子通過boost::bind定義了一個二元函數,用於std::sort演算法,該演算法要求一個二元函數作為其第三個參數。
#include <boost/bind.hpp> #include <vector> #include <algorithm> bool compare(int i, int j) { return i > j; } int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::sort(v.begin(), v.end(), boost::bind(compare, _1, _2)); }
因為使用了兩個預留位置_1 和_2,所以boost::bind()定義了一個二元函數。 std::sort()演算法以容器 v 的兩個元素來調用該函數,並根據傳回值來對容器進行排序。基於compare()函數的定義,容器將被按降序排列。
但是,由於compare()本身就是一個二元函數,所以使用boost::bind()確是多餘的。
#include <boost/bind.hpp> #include <vector> #include <algorithm> bool compare(int i, int j) { return i > j; } int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::sort(v.begin(), v.end(), compare); }
不過使用boost::bind()還是有意義的。例如,如果容器要按升序排列而又不能修改 compare()函數的定義。
#include <boost/bind.hpp> #include <vector> #include <algorithm> bool compare(int i, int j) { return i > j; } int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::sort(v.begin(), v.end(), boost::bind(compare, _2, _1)); }
該例子僅改變了預留位置的順序:_2 被作為第一參數傳遞,而 _1 則被作為第二參數傳遞至 compare(),這樣即可改變排序的順序。
二、Boost.Ref
本庫Boost.Ref通常與 Boost.Bind一起使用,所以我把它們挨著寫。它提供了兩個函數:boost::ref()和 boost::cref(),都定義於 boost/ref.hpp。
當要用於boost::bind()的函數帶有至少一個引用參數時,Boost.Ref就很重要了。 由於boost::bind()會複製它的參數,所以引用必須特別處理。
#include <boost/bind.hpp> #include <iostream> #include <vector> #include <algorithm> void add(int i, int j, std::ostream &os) { os << i + j << std::endl; } int main() { std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(2); std::for_each(v.begin(), v.end(), boost::bind(add, 10, _1, boost::ref(std::cout))); }
以上例子使用了上一節中的add()函數。 不過這一次該函數需要一個流對象的引用來列印資訊。 因為傳給boost::bind()的參數是以值方式傳遞的,所以std::cout不能直接使用,否則該函數會試圖建立它的一份拷貝。
通過使用模板函數boost::ref(),象std::cout這樣的流就可以被以引用方式傳遞,也就可以成功編譯上面這個例子了。
要以引用方式傳遞常量對象,可以使用模板函數boost::cref()。
三、Boost.Function
為了封裝函數指標,Boost.Function提供了一個名為boost::function的類。它定義於 boost/function.hpp,用法如下:
#include <boost/function.hpp> #include <iostream> #include <cstdlib> #include <cstring> int main() { boost::function<int (const char*)> f = std::atoi; std::cout << f("1609") << std::endl; f = std::strlen; std::cout << f("1609") << std::endl; }
boost::function可以定義一個指標,指向具有特定簽名的函數。以上例子定義了一個指標f,它可以指向某個接受一個類型為const char*的參數且返回一個類型為int的值的函數。定義完成後,匹配此簽名的函數均可賦值給這個指標。這個常式就是先將std::atoi()賦值給f,然後再將它重賦值為std::strlen()。
注意,給定的資料類型並不需要精確匹配,雖然std::strlen()是以std::size_t作為傳回型別的,但是它也可以被賦值給f。
因為f是一個函數指標,所以被賦值的函數可以通過重載的operator()()操作符來調用。取決於當前被賦值的是哪一個函數,在以上例子中將調用std::atoi()或std::strlen()。
如果f未賦予一個函數而被調用,則會拋出一個boost::bad_function_call異常。
#include <boost/function.hpp> #include <iostream> int main() { try { boost::function<int (const char*)> f; f(""); } catch (boost::bad_function_call &ex) { std::cout << ex.what() << std::endl; } }
注意,將值 0 賦給一個 boost::function 類型的函數指標,將會釋放當前所賦的函數。釋放之後再調用它也會導致 boost::bad_function_call 異常被拋出。要檢查一個函數指標是否被賦值某個函數,可以使用 empty() 函數或 operator bool() 操作符。
通過使用 Boost.Function,類成員函數也可以被賦值給類型為 boost::function 的對象。
#include <boost/function.hpp> #include <iostream> struct world { void hello(std::ostream &os) { os << "Hello, world!" << std::endl; } }; int main() { boost::function<void (world*, std::ostream&)> f = &world::hello; world w; f(&w, boost::ref(std::cout)); }
在調用這樣的一個函數時,傳入的第一個參數表示了該函數被調用的那個特定對象。因此,在模板定義中的左括弧後的第一個參數必須是該特定類的指標。 接下來的參數才是表示相應的成員函數的簽名。
這個程式還使用了來自Boost.Ref庫的boost::ref(),它提供了一個方便的機制向 Boost.Function傳遞引用。