四、Boost.Lambda
匿名函數 - 又稱為lambda函數 - 已經在多種程式設計語言中存在,但 C++ 除外。 不過在Boost.Lambda庫的協助下,現在在 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); }
這段程式接受容器 v 中的元素並使用 print() 函數將它們寫出到標準輸出資料流。 由於 print() 只是寫出一個簡單的 int,所以該函數的實現相當簡單。嚴格來說,它是如此地簡單,以致於如果可以在 std::for_each() 演算法裡面直接定義它的話,會更為方便;從而省去增加一個函數的需要。 另外一個好處是代碼更為緊湊,使得演算法與負責資料輸出的函數不是局部性分離的。 Boost.Lambda 正好使之成為現實。
#include <boost/lambda/lambda.hpp> #include <iostream> #include <vector> #include <algorithm> 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::cout << boost::lambda::_1 << "\n"); }
Boost.Lambda 提供了幾個結構來定義匿名函數。代碼就被置於執行的地方,從而省去將它封裝為一個函數再進行相應的函數調用的這些開銷。與原來的例子一樣,這個程式將容器v 的所有元素寫出至標準輸出資料流。
與 Boost.Bind 相類似,Boost.Lambda 也定義了三個預留位置,名為 _1, _2 和 _3。但與 Boost.Bind 不同的是,這些預留位置是定義在單獨的名字空間的。因此,該例中的第一個預留位置是通過 boost::lambda::_1 來引用的。 為了滿足編譯器的要求,必須包含相應的標頭檔 boost/lambda/lambda.hpp。
雖然代碼的位置位於 std::for_each() 的第三個參數處,看起來很怪異,但 Boost.Lambda 可以寫出正常的 C++ 代碼。 通過使用預留位置,容器 v 的元素可以通過 << 傳給 std::cout 以將它們寫出到標準輸出資料流。
雖然 Boost.Lambda 非常強大,但也有一些缺點。 要在以上例子中插入換行的話,必須用 "\n" 來替代 std::endl 才能成功編譯。 因為一元 std::endl 模板函數所要求的類型不同於 匿名函式 std::cout << boost::lambda::_1 的函數,所以在此不能使用它。
下一個版本的 C++ 標準很可能會將 匿名函式作為 C++ 語言本身的組成部分加入,從而消除對單獨的庫的需要。 但是在下一個版本到來並被不同的編譯器廠商所採用可能還需要好幾年。在此之前,Boost.Lambda 被證明是一個完美的替代品,從以下例子可以看出,這個例子只將大於1的元素寫出到標準輸出資料流。
#include <boost/lambda/lambda.hpp> #include <boost/lambda/if.hpp> #include <iostream> #include <vector> #include <algorithm> 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::lambda::if_then(boost::lambda::_1 > 1, std::cout << boost::lambda::_1 << "\n")); }
標頭檔 boost/lambda/if.hpp 定義了幾個結構,允許在 匿名函式內部使用if語句。最基本的結構是boost::lambda::if_then()模板函數,它要求兩個參數:第一個參數對條件求值,如果為真,則執行第二個參數。如例中所示,每個參數本身都可以是lambda函數。
除了boost::lambda::if_then(),Boost.Lambda還提供了 boost::lambda::if_then_else()和 boost::lambda::if_then_else_return()模板函數 - 它們都要求三個參數。 另外還提供了用於實現迴圈、轉型操作符,甚至是throw - 允許 匿名函式拋出異常 - 的模板函數。
雖然可以用這些模板函數在C++中構造出複雜的 匿名函式,但是你必須要考慮其它方面,如可讀性和可維護性。因為別人需要學習並理解額外的函數,如用 boost::lambda::if_then()來替代已知的 C++ 關鍵字 if 和 else,匿名函式的好處通常隨著它的複雜性而降低。 多數情況下,更為合理的方法是用熟悉的 C++ 結構定義一個單獨的函數。
五、練習題
1、簡化下面程式,將函數對象divided_by轉換為一個函數,並將for迴圈替換為用一個標準C++演算法來輸出:
#include <algorithm> #include <functional> #include <vector> #include <iostream> class divide_by : public std::binary_function<int, int, int> { public: int operator()(int n, int div) const { return n / div; } }; int main() { std::vector<int> numbers; numbers.push_back(10); numbers.push_back(20); numbers.push_back(30); std::transform(numbers.begin(),numbers.end(),numbers.begin(),std::bind2nd(divide_by(), 2)); for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) std::cout << *it << std::endl; }
2、簡化下面程式,將兩個for迴圈都替換為標準C++演算法:
#include <string> #include <vector> #include <iostream> int main() { std::vector<std::string> strings; strings.push_back("Boost"); strings.push_back("C++"); strings.push_back("Libraries"); std::vector<int> sizes; for (std::vector<std::string>::iterator it = strings.begin(); it != strings.end(); ++it) sizes.push_back(it->size()); for (std::vector<int>::iterator it = sizes.begin(); it != sizes.end(); ++it) std::cout << *it << std::endl; }
3、簡化下面程式,修改變數processors的類型,並將for迴圈替換為標準C++演算法:
#include <vector> #include <iostream> #include <cstdlib> #include <cstring> int main() { std::vector<int(*)(const char*)> processors; processors.push_back(std::atoi); processors.push_back(reinterpret_cast<int(*)(const char*)>(std::strlen)); const char data[] = "1.23"; for (std::vector<int(*)(const char*)>::iterator it = processors.begin(); it != processors.end(); ++it) std::cout << (*it)(data) << std::endl; }
六、解答
第1個題目是對boost::bind()函數的使用考察,題中的函數對象divide_by,是一個派生自binary_function的函數對象,實現的功能就是將容器中的每個元素除以2輸出。這裡用bind簡化,若不考慮第二個問題,程式可改為
#include <boost/bind.hpp>#include <iostream> #include <vector> #include <algorithm> #include <functional> void divide_by(int i,int j){std::cout<<i/j<<std::endl;}void main() { std::vector<int> numbers; numbers.push_back(10); numbers.push_back(20); numbers.push_back(30); std::for_each(numbers.begin(), numbers.end(), boost::bind(divide_by,_1,10)); }
但還要求將for替換為標準C++演算法輸出,就是考查Boost::Ref函數的使用,接著再加上ref,最終解答為:
#include <boost/bind.hpp>#include <iostream> #include <vector> #include <algorithm> void divide_by(int i,int j,std::ostream &os){os<<i/j<<std::endl;}void main() { std::vector<int> numbers; numbers.push_back(10); numbers.push_back(20); numbers.push_back(30); std::for_each(numbers.begin(), numbers.end(), boost::bind(divide_by,_1,10,boost::ref(std::cout))); }
第2題本身有點問題,有的編譯器可能會報it重複定義,可將第二個it改掉。另外,我用的boost庫是最新的,不知怎麼的不相容vc6.0了,已包含function.hpp就提示std::abort不支援,所以這題就直接寫的代碼,未測試。for迴圈用for_each代替,第一個for是擷取長度並存入sizes中,分別處理的for迴圈為
std::for_each(strings.begin(),strings.end(),sizes.push_back(boost::lambda::_1))
std::for_each(strings.begin(),strings.end(),std::cout<<boost::lambda::_1<<"\n")
第3題比較簡單,它的processors當然是要變成function了:
boost::function<int(*)(const char*)> processors;
processors = std::atoi;
const char data[] = "1.23";
for迴圈的處理同1。