<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 用法 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 既提供了一致的語法,也增加了標準庫目前缺少的功能。 ### 看一下門簾的后面 在你開始使用 Boost.Bind 后,這是無可避免的;你將開始驚訝它到底是如何工作的。這看起來就象是魔術,`bind` 可以推斷出參數的類型和返回類型,它又是如何處理占位符的呢?我們將快速地看一下驅動這個東西的機制。它有助于知道一點 `bind`的 工作原理,特別是在試圖解釋這驚人的簡潔性以及編譯器對最輕微的錯誤給出的直接的錯誤信息。我們將創建一個非常簡單的綁定器,至少是部分地模仿 Boost.Bind 的語法。為了避免把這個離題的討論搞成幾頁那么長,我們只支持一類綁定,即接受單個參數的成員函數。此外,我們不會對cv限定符進行處理;我們只處理最簡 單的情況。 首先,我們需要能夠推斷出我們要綁定的函數的返回類型、類的類型、和參數類型。我們用一個函數模板來做到這一點。 ``` template <typename R, typename T, typename Arg> simple_bind_t<R,T,Arg> simple_bind( R (T::*fn)(Arg), const T& t, const placeholder&) { return simple_bind_t<R,T,Arg>(fn,t); } ``` 這看起來有點可怕,畢竟這只是在定義整個機器的一部分。但是,這一部分的焦點在于類型推斷在哪發生。你會注意到這個函數有三個模板參數,`R`, `T`, 和 `Arg`. `R` 是返回的類型,`T` 是類的類型,而 `Arg` 是(單個)參數的類型。這些模板參數組成了我們的函數的第一個參數,即 `R (T::*f)(Arg)`. 這樣,傳遞一個帶單個參數的成員函數給 `simple_bind` 將允許編譯器推斷出 `R` 為成員函數的返回類型,`T` 為成員函數的類,`Arg` 為成員函數的參數類型。`simple_bind` 的返回類型是一個函數對象,它使用與 `simple_bind` 相同的三個類型進行特化,其構造函數接受一個成員函數指針和一個對應類(`T`)的實例。 `simple_bind` 簡單地忽略占位符(即函數的最后一個參數),我保留這個參數的原因是為了模仿 Boost.Bind 的語法。在一個更好的實現中,我們顯然應該使用這個參數,但是現在讓我們先不要管它。這個函數對象的實現相當簡單。 ``` template <typename R,typename T, typename Arg> class simple_bind_t { typedef R (T::*fn)(Arg); fn fn_; T t_; public: simple_bind_t(fn f,const T& t):fn_(f),t_(t) {} R operator()(Arg& a) { return (t_.*fn_)(a); } }; ``` 從 `simple_bind` 的實現中我們可以看到,構造函數接受兩個參數:第一個是指向成員函數的指針,第二個是一個 `const T` 引用,它會被復制并稍后用于給定一個用戶提供的參數來調用其成員函數。最后,調用操作符返回 `R`, 即成員函數的返回類型,并接受一個 `Arg` 參數,即傳給成員函數的那個參數的類型。調用成員函數的語法稍稍有點晦澀: ``` (t_.*fn_)(a); ``` `.*` 是成員指針操作符,它的第一個操作數是 `class T`; 另外還有一個成員指針操作符,`-&gt;*`, 它的第一個操作數是是一個 `T` 指針。剩下就是創建一個占位符,即用于替換實際參數的變量。我們可以通過在匿名名字空間中包含某種類型的變量來創建一個占位符;我們把它稱為 `placeholder`: ``` namespace { class placeholder {}; placeholder _1; } ``` 我們創建一個簡單的類和一個小程序來測試一下。 ``` class Test { public: void do_stuff(const std::vector<int>& v) { std::copy(v.begin(),v.end(), std::ostream_iterator<int>(std::cout," ")); } }; int main() { Test t; std::vector<int> vec; vec.push_back(42); simple_bind(&Test::do_stuff,t,_1)(vec); } ``` 當我們用上述參數實例化函數 `simple_bind` 時,類型被自動推斷;`R` 是 `void`, `T` 是 `Test`, 而 `Arg` 是一個 `const std::vector&lt;int&gt;` 引用。函數返回一個 `simple_bind_t&lt;void,Test,Arg&gt;` 的實例,我們立即調用它的調用操作符,并傳進一個參數 `vec`. 非常不錯,`simple_bind` 已經給了你關于綁定器如何工作的一些想法。現在,是時候回到 Boost.Bind 了! ### 關于占位符和參數 第一個例子示范了 `bind` 最多可以支持九個參數,但了解多一點關于參數和占位符如何工作的情況,可以讓我們更好地使用它。首先,很重要的一點是,普通函數與成員函數之間有著非常大的差異,在綁定一個成員函數時,`bind` 表達式的第一個參數必須是成員函數所在類的實例!理解這個規則的最容易的方法是,這個顯式的參數將取替隱式的 `this` ,被傳遞給所有的非靜態成員函數。細心的讀者將會留意到,實際上這意味著對于成員函數的綁定器來說,只能支持八個參數,因為第一個要用于傳遞實際的對象。以下例子定義了一個普通函數 `print_string` 和一個帶有成員函數 `print_string` 的類 `some_class` ,它們將被用于 `bind` 表達式。 ``` #include <iostream> #include <string> #include "boost/bind.hpp" class some_class { public: typedef void result_type; void print_string(const std::string& s) const { std::cout << s << '\n'; } }; void print_string(const std::string s) { std::cout << s << '\n'; } int main() { (boost::bind(&print_string,_1))("Hello func!"); some_class sc; (boost::bind(&some_class::print_string,_1,_2)) (sc,"Hello member!"); } ``` 第一個 `bind` 表達式綁定到普通函數 `print_string`. 因為該函數要求一個參數,因此我們需要用一個占位符(`_1`)來告訴 `bind` 它的哪一個參數將被傳遞為 `print_string` 的第一個參數。要調用獲得的函數對象,我們必須傳遞一個 `string` 參數給調用操作符。參數是一個 `const std::string&`, 因此傳遞一個字面的字符串將引發一個 `std::string` 轉型構造函數的調用。 ``` (boost::bind(&print_string,_1))("Hello func!"); ``` 第二個綁定器用于一個成員函數,`some_class` 的 `print_string` 。`bind` 的第一個參數是成員函數指針。但是,一個非靜態成員函數指針并不真的是一個指針\[3\]。我們必須要有一個對象才可以調用這個函數。這就是為什么這個 `bind` 表達式必須聲明綁定器有兩個參數,調用它時兩個參數都必須提供。 > \[3\] 是的,我知道這聽起來很怪異。但它的確是真的。 ``` boost::bind(&some_class::print_string,_1,_2); ``` 要看看為什么會這樣,就要考慮一下得到的這個函數對象要怎么使用。我們必須把一個 `some_class` 實例和一個 `print_string` 用的參數一起傳遞給它。 ``` (boost::bind(&some_class::print_string,_1,_2))(sc,"Hello member!"); ``` 這個調用操作符的第一個參數是 `this` ,即那個 `some_class` 實例。注意,這第一個參數可以是一個指針(智能的或裸的)或者是一個引用;`bind` 是非常隨和的。調用操作符的第二個參數是那個成員函數要用的參數。這里,我們"延遲"了所有兩個參數,即我們定義的這個綁定器,它的兩個參數,對象本身及 成員函數的參數,都要在調用操作符時才指定。我們不是一定非這樣做不可。例如,我們可以創建一個綁定器,每次調用它時,都是對同一個對象調用 `print_string` ,就象這樣: ``` (boost::bind(&some_class::print_string,some_class(),_1)) ("Hello member!"); ``` 這次得到的函數對象已經包含了一個 `some_class` 實例,因此它的調用操作符只需要一個占位符(`_1`)和一個參數(一個string)。最后,我們還可以創建一個所謂的無參(nullary)函數,它連那個 string 也綁定了,就象這樣: ``` (boost::bind(&some_class::print_string, some_class(),"Hello member!"))(); ``` 這些例子清楚地顯示了 `bind` 的多功能性。它可用于延遲它所封裝的函數的所有參數、部分參數、或一個參數也不延遲。它也可以把參數按照你所要的順序進行重排;只要照你的需要排列占位符就行了。接下來,我們將看看如何用 `bind` 來就地創建排序用的謂詞。 ### 動態的排序標準 在對容器中的元素進行排序時,我們有時候需要創建一個函數對象以定義排序的標準,如果我們沒有提供關系操作符,或者是已有的關系操作符不是我們想要的排序標準時,就需要這樣做了。有些時候我們可以使用來自標準庫的比較函數對象(`std::greater`, `std::greater_equal`, 等等),但只能對已有類型進行比較,我們不能就地定義一個新的。我們將使用一個名為 `personal_info` 的類來演示 Boost.Bind 如何幫助我們。`personal_info` 包含有 first name, last name, 和 age, 并且它沒有提供任何的比較操作符。這些信息在創建以后就不再變動,并且可以用成員函數 `name`, `surname`, 和 `age` 來取出。 ``` class personal_info { std::string name_; std::string surname_; unsigned int age_; public: personal_info( const std::string& n, const std::string& s, unsigned int age):name_(n),surname_(s),age_(age) {} std::string name() const { return name_; } std::string surname() const { return surname_; } unsigned int age() const { return age_; } }; ``` 我們通過提供以下操作符來讓這個類可以流輸出(OutputStreamable): ``` std::ostream& operator<<( std::ostream& os,const personal_info& pi) { os << pi.name() << ' ' << pi.surname() << ' ' << pi.age() << '\n'; return os; } ``` 如果我們要對含有類型 `personal_info` 元素的容器進行排序,我們就需要為它提供一個排序謂詞。為什么開始的時候我們沒有為 `personal_info` 提供關系操作符呢?一個原因是,因為有幾種排序的可能性,而我們不知道對于不同的用戶哪一種是合適的。雖然我們也可以選擇為不同的排序標準提供不同的成員 函數,但這樣會加重負擔,我們要在類中實現所有相關的排序標準,這并不總是可以做到的。幸運的是,我們可以很容易地用 `bind` 就地創建所需的謂詞。我們先看看基于年齡(可以通過成員函數 `age` 取得)來進行排序。我們可以為此創建一個函數對象。 ``` class personal_info_age_less_than : public std::binary_function< personal_info,personal_info,bool> { public: bool operator()( const personal_info& p1,const personal_info& p2) { return p1.age()<p2.age(); } }; ``` 我們讓 `personal_info_age_less_than` 公有派生自 `binary_function`. 從 `binary_function` 派生可以提供使用適配器時所需的 `typedef` ,例如使用 `std::not2`. 假設有一個 `vector`, `vec`, 含有類型為 `personal_info` 的元素,我們可以象這樣來使用這個函數對象: ``` std::sort(vec.begin(),vec.end(),personal_info_age_less_than()); ``` 只要不同的比較方式的數量很有限,這種方式就可以工作良好。但是,有一個潛在的問題,計算邏輯被定義 在不同的地方,這會使得代碼難以理解。利用一個較長的、描述清晰的名字可以解決這個問題,就象我們在這里做的一樣,但是不是所有情況都會這樣清晰,有很大 可能我們需要為大于、小于或等于關系提供一堆的函數對象。 那么,Boost.Bind 有什么幫助呢?實際上,在這個例子中它可以幫助我們三次。如果我們要解決這個問題,我們發現有三件事情要做,第一件是綁定一個邏輯操作,如 `std::less`. 這很容易,我們可以得到第一部分代碼。 ``` boost::bind<bool>(std::less<unsigned int>(),_1,_2); ``` 注意,我們通過把 `bool` 參數提供給 `bind`,顯式地給出了返回類型。有時這是需要的,對于有缺陷的編譯器或者在無法推斷出返回類型的上下文時。如果一個函數對象包含 `typedef`, `result_type`, 就不需要顯式給出返回類型\[4\]。現在,我們有了一個接受兩個參數的函數對象,兩個參數的類型都是 `unsigned int`, 但我們還不能用它,因為容器中的元素的類型是 `personal_info`, 我們需要從這些元素中取出 age 并把它作為參數傳遞給 `std::less`. 我們可以再次使用 `bind` 來實現。 > \[4\] 標準庫的函數對象都定義了 `result_type` ,因此它們可以與 `bind` 的返回類型推斷機制共同工作。 ``` boost::bind( std::less<unsigned int>(), boost::bind(&personal_info::age,_1), boost::bind(&personal_info::age,_2)); ``` 這里,我們創建了另外兩個綁定器。第一個用主綁定器的調用操作符的第一個參數(`_1`)來調用 `personal_info::age` 。第二個用主綁定器的調用操作符的第二個參數(`_2`)來調用 `personal_info::age` 。因為 `std::sort` 傳遞兩個 `personal_info` 對象給主綁定器的調用操作符,結果就是對來自被排序的 `vector` 的兩個 `personal_info` 分別調用 `personal_info::age` 。最后,主綁定器傳遞兩個新的、內層的綁定器的調用操作符所返回的 age 給 `std::less`. 這正是我們所需要的!調用這個函數對象的結果就是 `std::less` 的結果,這意味著我們有了一個有效的比較函數對象可以用來排序容器中的 `personal_info` 對象。以下是使用它的方法: ``` std::vector<personal_info> vec; vec.push_back(personal_info("Little","John",30)); vec.push_back(personal_info("Friar", "Tuck",50)); vec.push_back(personal_info("Robin", "Hood",40)); std::sort( vec.begin(), vec.end(), boost::bind( std::less<unsigned int>(), boost::bind(&personal_info::age,_1), boost::bind(&personal_info::age,_2))); ``` 我們可以簡單地通過綁定另一個 `personal_info` 成員(變量或函數)來進行不同的排序,例如,按 last name 排序。 ``` std::sort( vec.begin(), vec.end(), boost::bind( std::less<std::string>(), boost::bind(&personal_info::surname,_1), boost::bind(&personal_info::surname,_2))); ``` 這是一種出色的技術,因為它提供了一個重要的性質:就地實現簡單的函數。它使得代碼易懂且易于維護。雖然技術上可以用綁定器實現基于復雜標準的排序,但那樣做是不明智的。給 `bind` 表達式添加復雜的邏輯會很快失去它的清晰和簡潔。雖然有時你想用綁定來做更多的事情,但最好是讓綁定器與要維護它的人一樣聰明,而不是更加聰明。 ### 函數組合,Part I 一個常見的問題是,將一些函數或函數對象組合成一個函數對象。假設你需要測試一個 `int` ,看它是否大于5且小于等于10。使用"常規"的代碼,你將這樣寫: ``` if (i>5 && i<=10) { // Do something } ``` 如果是處理一個容器中的元素,上述代碼只有放在一個單獨的函數時才能工作。如果你不想這樣,那么用一個嵌套的 `bind` 也可以獲得相同的效果(注意,這時通常不能使用標準庫的 `bind1st` 和 `bind2nd`)。如果我們對這個問題進行分解,我們會發現我們需要:邏輯與(`std::logical_and`),?大于(`std::greater`), 和小于等于(`std::less_equal`)。邏輯與看起來就象這樣: ``` boost::bind(std::logical_and<bool>(),_1,_2); ``` 然后,我們需要另一個謂詞來回答 `_1` 是否大于5。 ``` boost::bind(std::greater<int>(),_1,5); ``` 然后,我們還需要另一個謂詞來回答 `_1` 是否小于等于10。 ``` boost::bind(std::less_equal<int>(),_1,10); ``` 最后,我們需要把它們兩個用邏輯與合起來,就象這樣: ``` boost::bind( std::logical_and<bool>(), boost::bind(std::greater<int>(),_1,5), boost::bind(std::less_equal<int>(),_1,10)); ``` 這樣一個嵌套的 `bind` 相對容易理解,雖然它是后序的。還有,任何人都可以逐字地閱讀這段代碼并弄清楚它的意圖。我們用一個例子來測試一下這個綁定器。 ``` std::vector<int> ints; ints.push_back(7); ints.push_back(4); ints.push_back(12); ints.push_back(10); int count=std::count_if( ints.begin(), ints.end(), boost::bind( std::logical_and<bool>(), boost::bind(std::greater<int>(),_1,5), boost::bind(std::less_equal<int>(),_1,10))); std::cout << count << '\n'; std::vector<int>::iterator int_it=std::find_if( ints.begin(), ints.end(), boost::bind(std::logical_and<bool>(), boost::bind(std::greater<int>(),_1,5), boost::bind(std::less_equal<int>(),_1,10))); if (int_it!=ints.end()) { std::cout << *int_it << '\n'; } ``` 使用嵌套的 `bind` 時,小心地對代碼進行正確的縮入非常重要,因為如果一旦縮入錯誤,代碼就會很難理解。想想前面那段清晰的代碼,再看看以下這個容易混亂的例子。 ``` std::vector<int>::iterator int_it= std::find_if(ints.begin(),ints.end(), boost::bind<bool>( std::logical_and<bool>(), boost::bind<bool>(std::greater<int>(),_1,5), boost::bind<bool>(std::less_equal<int>(),_1,10))); ``` 當然,對于較長的代碼行,這是一個常見的問題,但是在使用這里所描述的結構時更為明顯,在這里長語句是合理的而不是個別例外。因此,請對你之后的程序員友好些,確保你的代碼行正確縮入,這樣可以讓人更容易閱讀。 本書的一位認真的審閱者曾經問過,在前面的例子中,為什么創建了兩個相同的綁定器,而不是創建一個綁定器對象然后使用兩次?答案是,因為我們不知道 `bind` 所創建的綁定器的精確類型(它是由實現定義的),我們沒有方法為它聲明一個變量。還有,這個類型通常都非常復雜,因為它的署名特征包括了函數 `bind` 中所有的類型信息(自動推斷的)。但是,可以用另外一個工具來保存得到的函數對象,例如來自 Boost.Function 的工具。相關方法的詳情請見 "[Library 11](../Text/content.html#ch11): [Function 11](../Text/content.html#ch11)"。 這里給出的函數組合的要點與標準庫的一個著名的擴充相符,即來自SGI STL的函數 `compose2` ,它在 Boost.Compose 庫(現在已經不用了)中也被稱為 `compose_f_gx_hx` 。 ### 函數組合,Part II 在SGI STL中的另一個常用的函數組合是 `compose1` ,在 Boost.Compose 中是 `compose_f_gx` 。這些函數提供了用一個參數調用兩個函數的方法,把最里面的函數返回的結果傳遞給第一個函數。有時一個例子勝過千言萬語,設想你需要對容器中的浮點數元素 執行兩個算術操作。我們首先把值增加10%,然后再減少10%;這個例子對于少數工作在財政部門的人來說可能是有用的一課。 ``` std::list<double> values; values.push_back(10.0); values.push_back(100.0); values.push_back(1000.0); std::transform( values.begin(), values.end(), values.begin(), boost::bind( std::multiplies<double>(),0.90, boost::bind<double>( std::multiplies<double>(),_1,1.10))); std::copy( values.begin(), values.end(), std::ostream_iterator<double>(std::cout," ")); ``` 你怎么知道哪個嵌套的 `bind` 先被調用呢?你也許已經注意到,總是最里面的 `bind` 先被求值。這意味著我們可以把同樣的代碼寫得稍微有點不同。 ``` std::transform( values.begin(), values.end(), values.begin(), boost::bind<double>( std::multiplies<double>(), boost::bind<double>( std::multiplies<double>(),_1,1.10),0.90)); ``` 這里,我們改變了傳給 `bind` 的參數的順序,把第一個 `bind` 的參數加在了表達式的最后。雖然我不建議這樣做,但它對于理解參數如何傳遞給 `bind` 函數很有幫助。 ### bind 表達式中的是值語義還是指針語義? 當我們傳遞某種類型的實例給一個 `bind` 表達式時,它將被復制,除非我們顯式地告訴 `bind` 不要復制它。要看我們怎么做,這可能是至關重要的。為了看一下在我們背后發生了什么事情,我們創建一個 `tracer` 類,它可以告訴我們它什么時候被缺省構造、被復制構造、被賦值,以及被析構。這樣,我們就可以很容易看到用不同的方式使用 `bind` 會如何影響我們傳送的實例。以下是完整的 `tracer` 類。 ``` class tracer { public: tracer() { std::cout << "tracer::tracer()\n"; } tracer(const tracer& other) { std::cout << "tracer::tracer(const tracer& other)\n"; } tracer& operator=(const tracer& other) { std::cout << "tracer& tracer::operator=(const tracer& other)\n"; return *this; } ~tracer() { std::cout << "tracer::~tracer()\n"; } void print(const std::string& s) const { std::cout << s << '\n'; } }; ``` 我們把我們的 `tracer` 類用于一個普通的 `bind` 表達式,象下面這樣。 ``` tracer t; boost::bind(&tracer::print,t,_1) (std::string("I'm called on a copy of t\n")); ``` 運行這段代碼將產生以下輸出,可以清楚地看到有很多拷貝產生。 ``` tracer::tracer() tracer::tracer(const tracer& other) tracer::tracer(const tracer& other) tracer::tracer(const tracer& other) tracer::~tracer() tracer::tracer(const tracer& other) tracer::~tracer() tracer::~tracer() I'm called on a copy of t tracer::~tracer() tracer::~tracer() // 譯注:原文沒有這一行,有誤 ``` 如果我們使用的對象的拷貝動作代價昂貴,我們也許就不能這樣用 `bind` 了。但是,拷貝還是有優點的。它意味著 `bind` 表達式以及由它所得到的綁定器不依賴于原始對象(在這里是 `t`)的生存期,這通常正是想要的。要避免復制,我們必須告訴 `bind` 我們想傳遞引用而不是它所假定的傳值。我們要用 `boost::ref` 和 `boost::cref` (分別用于引用和 `const` 引用)來做到這一點,它們也是 Boost.Bind 庫的一部分。對我們的 `tracer` 類使用 `boost::ref` ,測試代碼現在看起來象這樣: ``` tracer t; boost::bind(&tracer::print,boost::ref(t),_1)( std::string("I'm called directly on t\n")); ``` Executing the code gives us this: ``` tracer::tracer() I'm called directly on t tracer::~tracer() // 譯注:原文為 tracer::~tracer,有誤 ``` 這正是我們要的,避免了無謂的復制。`bind` 表達式使用原始的實例,這意味著沒有 `tracer` 對象的拷貝了。當然,它同時也意味著綁定器現在要依賴于 `tracer` 實例的生存期了。還有一種避免復制的方法;就是通過指針來傳遞參數而不是通過值來傳遞。 ``` tracer t; boost::bind(&tracer::print,&t,_1)( std::string("I'm called directly on t\n")); ``` 因此說,`bind` 總是執行復制。如果你通過值來傳遞,對象將被復制,這可能對性能有害或者產生不必要的影響。為了避免復制對象,你可以使用 `boost::ref`/`boost::cref` 或者使用指針語義。 ### 虛擬函數也可以綁定 到目前為止,我們看到了 `bind`如何可以用于非成員函數和非虛擬成員函數,但是 它也可以用于綁定一個虛擬成員函數。通過 Boost.Bind, 你可以象使用非虛擬函數一樣使用虛擬函數,即把它綁定到最先聲明該成員函數為虛擬的基類的那個虛擬函數上。這個綁定器就可以用于所有的派生類。如果你綁定 到其它派生類,你就限制了可以使用這個綁定器的類\[5\]。考慮以下兩個類 `base` 和 `derived` : > \[5\] 這與聲明一個類指針來調用虛擬函數沒有什么不同。指針指向的派生類越靠近底層,則越少的類可以綁定到指針。 ``` class base { public: virtual void print() const { std::cout << "I am base.\n"; } virtual ~base() {} }; class derived : public base { public: void print() const { std::cout << "I am derived.\n"; } }; ``` 我們可以用這兩個類對綁定到虛擬函數進行測試,如下: ``` derived d; base b; boost::bind(&base::print,_1)(b); boost::bind(&base::print,_1)(d); ``` 運行這段代碼可以清楚地看到結果正是我們所希望的。 ``` I am base. I am derived. ``` 對于可以支持虛擬函數,你應該不會驚訝,現在我們已經示范了它和其它函數一樣運行。有一個相關的注意事項,如果你 `bind` 了一個成員函數而后來它被一個派生類重新定義了,或者一個虛擬函數在基類中是公有的而在派生類中變成了私有的,那么會發生什么呢?還可以正常工作嗎?如果可以,你希望是哪一種行為呢?是的,不管你是否使用 Boost.Bind,行為都不會有變化。因面,如果你 `bind`到 一個在其它類中被重新定義的函數,即它不是虛擬的并且派生類有一個相同特征的成員函數,那么基類中的版本將被調用。如果函數被隱藏,綁定器依然會被執行, 因為它顯式地訪問類型中的函數,這樣即使是被隱藏的成員函數也可以使用。最后,如果虛擬函數在基類中聲明為公有的,但在派生類中變成了私有的,那么對一個 派生類實例調用該函數將會成功,因為訪問是通過一個基類實例產生的,而基類的成員函數是公有的。當然,這種情況顯示出設計的確是有問題的。 ### 綁定到成員變量 很多時候你需要 `bind` 數據成員而不是成員函數。例如,使用 `std::map` 或 `std::multimap` 時,元素的類型是 `std::pair&lt;key const,data&gt;`, 但你想使用的信息通常不是 key, 而是 data. 假設你想把一個 `map` 中的每個元素傳遞給一個函數,它接受單個 data 類型的參數。你需要創建一個綁定器,它把每個元素(類型為 `std::pair`)的 `second` 成員傳給綁定的函數。以下代碼舉例說明如何實現: ``` void print_string(const std::string& s) { std::cout << s << '\n'; } std::map<int,std::string> my_map; my_map[0]="Boost"; my_map[1]="Bind"; std::for_each( my_map.begin(), my_map.end(), boost::bind(&print_string, boost::bind( &std::map<int,std::string>::value_type::second,_1))); ``` 你可以 `bind` 到一個成員變量,就象你可以綁定一個成員函數或普通函數一樣。要注意的是,要使得代碼更易讀(和寫),使用短的、方便的名字是個好主意。在前例中,對 `std::map`?使用一個 `typedef` 有助于提高可讀性。 ``` typedef std::map<int,std::string> map_type; boost::bind(&map_type::value_type::second,_1))); ``` 雖然需要 `bind` 到成員變量的時候沒有象成員函數那么多,但是可以這樣做還是很方便的。SGI STL (及其派生的庫)的用戶可能很熟悉 `select1st` 和 `select2nd` 函數。它們用于選出 `std::pair` 的 `first` 或 `second` 成員,與我們在這個例子中所做的一樣。注意,`bind` 可以用于任意類型和任意名字。 ### 綁定還是不綁定 Boost.Bind 庫帶來了很大的靈活性,但是也給程序員帶來了挑戰,因為有些時候本應該使用獨立的函數對象的,但也會讓人傾向于使用綁定器。許多工作可以也應該利用 Bind 來完成,但過度使用也是一種錯誤,應該在代碼開始變得難以閱讀、理解和維護的地方畫一條分界線。不幸的是,分界線的位置是由分享(閱讀、維護和擴展)代碼的程序員所決定的,他們的經驗決定了什么是可以接受的,什么不是。使用專門的函數對象的好處是,它們通常是無需加以說明的,而使用綁定器來提供同樣清楚的信息則是一項我們必須堅持克服的挑戰。例如,如果你需要創建一個你都很難弄明白的嵌套 `bind` ,有可能就是你已經過度使用了。讓我們用代碼來解釋一下。 ``` #include <iostream> #include <string> #include <map> #include <vector> #include <algorithm> #include "boost/bind.hpp" void print(std::ostream* os,int i) { (*os) << i << '\n'; } int main() { std::map<std::string,std::vector<int> > m; m["Strange?"].push_back(1); m["Strange?"].push_back(2); m["Strange?"].push_back(3); m["Weird?"].push_back(4); m["Weird?"].push_back(5); std::for_each(m.begin(),m.end(), boost::bind(&print,&std::cout, boost::bind(&std::vector<int>::size, boost::bind( &std::map<std::string, std::vector<int> >::value_type::second,_1)))); } ``` 上面這段代碼實際上做了什么?有的人可以流暢地閱讀這段代碼\[6\],但對于我們多數人來說,需要一些時間才能搞清楚它是干嘛的。是的,綁定器對 `pair` (即 `std::map&lt;std::string,std::vector&lt;int&gt; &gt;::value_type`)的成員 `second` 調用成員函數 `size`?。這種情況下,簡單的問題被綁定器弄得復雜了,創建一個小的函數對象來取代這個讓人難以理解的復雜綁定器是更好的選擇。一個可以完成相同工作的簡單函數對象如下: > \[6\] 你好,Peter Dimov. ``` class print_size { std::ostream& os_; typedef std::map<std::string,std::vector<int> > map_type; public: print_size(std::ostream& os):os_(os) {} void operator()( const map_type::value_type& x) const { os_ << x.second.size() << '\n'; } }; ``` 這種時候使用函數對象的最大好處就是,名字是無需加以說明的。 ``` std::for_each(m.begin(),m.end(),print_size(std::cout)); ``` 我們把這些(函數對象以及實際調用的所有代碼)和前面使用綁定器的版本作一下比較。 ``` std::for_each(m.begin(),m.end(), boost::bind(&print,&std::cout, boost::bind(&std::vector<int>::size, boost::bind( &std::map<std::string, std::vector<int> >::value_type::second,_1)))); ``` 或者,如果我們負點責任,為 `vector` 和 `map` 分別創建一個簡潔的 `typedef` : ``` std::for_each(m.begin(),m.end(), boost::bind(&print,&std::cout, boost::bind(&vec_type::size, boost::bind(&map_type::value_type::second,_1)))); ``` 這樣可以容易點分析,但它還是有點長。 雖然使用 `bind` 版本是有一些好理由,但我想觀點是很清楚的,綁定器不是非用不可的工具,使用時應該負責任,要讓它們物有所值。這一點在使用標準庫的容器和算法時非常、非常普遍。當事情變得太過復雜時,就回到老風格的方法上。 ### 讓綁定器把握狀態 創建一個象 `print_size` 那樣的函數對象時,有幾個選項可用。我們在上一節中創建的那個版本中,保存了一個到 `std::ostream` 的引用,并使用這個 `ostream` 來打印 `map_type::value_type` 參數的成員 `second` 的 `size` 函數的返回值。以下是原來的 `print_size` : ``` class print_size { std::ostream& os_; typedef std::map<std::string,std::vector<int> > map_type; public: print_size(std::ostream& os):os_(os) {} void operator()( const map_type::value_type& x) const { os_ << x.second.size() << '\n'; } }; ``` 要重點關注的一點是,這個類是有狀態的,狀態就在于那個保存的 `std::ostream`. 我們可以通過向調用操作符增加一個 `ostream` 參數來去掉這個狀態。這意味著這個函數對象將變為無狀態的。 ``` class print_size { typedef std::map<std::string,std::vector<int> > map_type; public: typedef void result_type; result_type operator()(std::ostream& os, const map_type::value_type& x) const { os << x.second.size() << '\n'; } }; ``` 注意,這個版本的 `print_size` 可以很好地用于 `bind`, 因為它增加了一個 `result_type typedef`. 這樣用戶在使用 `bind` 時就不需要顯式聲明函數對象的返回類型。在這個新版本的 `print_size` 里,用戶需要傳遞一個 `ostream` 參數來調用它。這在使用綁定器時是很容易的。用這個新的 `print_size` 重寫前節中的例子,我們可以得到: ``` #include <iostream> #include <string> #include <map> #include <vector> #include <algorithm> #include "boost/bind.hpp" // 省略 print_size 的定義 int main() { typedef std::map<std::string,std::vector<int> > map_type; map_type m; m["Strange?"].push_back(1); m["Strange?"].push_back(2); m["Strange?"].push_back(3); m["Weird?"].push_back(4); m["Weird?"].push_back(5); std::for_each(m.begin(),m.end(), boost::bind(print_size(),boost::ref(std::cout),_1)); } ``` 細心的讀者可能覺得為什么 `print_size` 不是一個普通函數,畢竟它已經不帶有任何狀態了。事實上,它可以是普通函數。 ``` void print_size(std::ostream& os, const std::map<std::string,std::vector<int> >::value_type& x) { os << x.second.size() << '\n'; } ``` 還有更多的泛化工作可以做。我們當前版本的 `print_size` 要求其調用操作符的第二個參數是一個 `const std::map&lt;std::string,std::vector&lt;int&gt; &gt;` 引用,這不夠通用。我們可以做得更好一些,讓調用操作符對這個類型進行泛化。這樣,`print_size` 就可以使用任意類型的參數,只要該參數含有名為 `second` 的公有成員,并且該成員有一個成員函數 `size`. 以下是改進后的版本: ``` class print_size { public: typedef void result_type; template <typename Pair> result_type operator() (std::ostream& os,const Pair& x) const { os << x.second.size() << '\n'; } }; ``` 這個版本的用法與前一個是一樣的,但它更為靈活。在創建可用于 `bind` 表達式的函數對象時,這種泛化更為重要。因為這樣的函數對象可用的情形將顯著增加,多數潛在的泛化都是值得做的。既然如此,我們還可以進一步放松對使用 `print_size` 的類型的要求。當前版本的 `print_size` 要求調用操作符的第二個參數是一個類似于 pair 的對象,即一個含有名為 `second` 的成員的對象。如果我們決定只要求這個參數含有成員函數 `size`, 這個函數對象就真的與它的名字相符了。 ``` class print_size { public: typedef void result_type; template <typename T> void operator() (std::ostream& os,const T& x) const { os << x.size() << '\n'; } }; ``` 當然,盡管 `print_size` 現在是與它的名字相符了,但是我們也要求用戶要做的更多了。象對于我們前面的例子,就需要手工綁定一個 `map_type::value_type::second`. ``` std::for_each(m.begin(),m.end(), boost::bind(print_size(),boost::ref(std::cout), boost::bind(&map_type::value_type::second,_1))); ``` 在使用 `bind` 時,通常都需要這樣的折衷,泛化只能到此為止,不要損害到可用性。如果我們走到極端,甚至去掉對成員函數 `size` 的要求,那么我們就轉了一圈,回到了我們開始的地方,又回到那個對多數程序員而言都過于復雜的 `bind` 表達式了。 ``` std::for_each(m.begin(),m.end(), boost::bind(&print<sup class="docfootnote">\[7\]</sup>,&std::cout, boost::bind(&vec_type::size, boost::bind(&map_type::value_type::second,_1)))); ``` > \[7\]?`print` 函數顯然也是需要的,沒有 lambda 工具。 ### 關于 Boost.Bind 和 Boost.Function 雖然本章中討論的內容應該沒有遺漏了,但是對于 Boost.Bind 和另一個庫,Boost.Function,之間的配合還是值得一提,它可以提供更多的功能。我們將在 "[Library 11](../Text/content.html#ch11):[Function 11](../Text/content.html#ch11)" 看到,不過我還是想給你一些提示。正如我們所看到的,沒有一個明顯的方法來保存我們的綁定器以備后用,我們只知道它們是帶有某些(未知)的特征的兼容函數 對象。但是,如果使用 Boost.Function, 保存函數用于以后的調用正是那個庫要做的,并且它兼容于 Boost.Bind, 可以把綁定器賦值給函數,保存它們并用于以后的調用。這是一個非常有用的概念,它可以用于適配并提高了松耦合。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看