【Boost】boost库中function和bind一起使用的技巧(二)
與 Boost.Function 一起使用 Boost.Bind ?
當我們把 Boost.Function 與某個支持參數綁定的庫結合起來使用時,事情變得更為有趣。Boost.Bind 為普通函數、成員函數以及成員變量提供參數綁定。這非常適合于 Boost.Function, 我們常常需要這類綁定,由于我們使用的類本身并不是函數對象。那么,我們用 Boost.Bind 把它們轉變為函數對象,然后我們可以用 Boost.Function 來保存它們并稍后調用。在將圖形用戶界面(GUIs)與如何響應用戶的操作進行分離時,幾乎總是要使用某種回調方法。如果這種回調機制是基于函數指針的,就很難避免對可以使用回調的類型的某些限制,也就增加了界面表現與業務邏輯之間的耦合風險。通過使用 Boost.Function,我們可以避免這些事情,并且當與某個支持參數綁定的庫結合使用時,我們可以輕而易舉地把上下文提供給調用的函數。這是本庫最常見的用途之一,把業務邏輯即從表示層分離出來。
以下例子包含一個藝術級的磁帶錄音機,定義如下:
[cpp] view plaincopy這個磁帶錄音機可以從一個GUI進行控制,或者也可能從一個腳本客戶端進行控制,或者從別的源進行控制,這意味著我們不想把這些函數的執行與它們的實現耦合起來。建立這種分離的一個常用的方法是,用專門的對象負責執行命令,而讓客戶對命令如何執行毫無所知。這也被稱為命令模式(Command?pattern),并且在它非常有用。這種模式的特定實現中的一個問題是,需要為每個命令創建單獨的類。以下片斷示范了它看起來是個什么樣子:
[cpp] view plaincopy這并不是一個非常吸引的方案,因為它使得代碼膨脹,有許多簡單的命令類,而它們只是簡單地負責調用一個對象的單個成員函數。有時候,這是必需的,因為這些命令可能需要實現業務邏輯和調用函數,但通常它只是由于我們所使用的工具有所限制而已。這些命令類可以這樣使用:
[cpp] view plaincopy現在,不用再創建額外的具體的命令類,如果我們實現的命令都是調用一個返回?void?且沒有參數(先暫時忽略函數 record, 它帶有一個參數)的成員函數的話,我們可以來點泛化。不用再創建一組具體的命令,我們可以在類中保存一個指向正確成員函數的指針。這是邁向正確方向[6]的一大步,就象這樣:
[6]?雖然損失了一點效率。
[cpp] view plaincopy這個命令模式的實現要好多了,因為它不需要我們再創建一組完成相同事情的獨立的類。這里的不同在于我們保存了一個?tape_recorder?成員函數指針在?func_?中,它要在構造函數中提供。命令的執行部分可能并不是你要展現給你的朋友看的東西,因為成員指針操作符對于一些人來說可能還不太熟悉。但是,這可以被看為一個低層的實現細節,所以還算好。有了這個類,我們可以進行泛化處理,不再需要實現單獨的命令類。
[cpp] view plaincopy你可能還沒有理解,我們已經在開始實現一個簡單的?boost::function?版本,它已經可以做到我們想要的。不要重復發明輪子,讓我們重點關注手邊的工作:分離調用與實現。以下是一個全新實現的?command?類,它更容易編寫、維護以及理解。
[cpp] view plaincopy通過使用 Boost.Function,我們可以立即從同時兼容函數和函數對象——包括由綁定器生成的函數對象——的靈活性之中獲益。這個?command?類把函數保存在一個返回?void?且不接受參數的?boost::function?中。為了讓這個類更加靈活,我們提供了在運行期修改函數對象的方法,使用一個泛型的成員函數,set_function.
[cpp] view plaincopy通過使用泛型方法,任何函數、函數對象,或者綁定器都兼容于我們的?command?類。我們也可以選擇把?boost:: function?作為參數,并使用?function?的轉型構造函數來達到同樣的效果。這個?command?類非常通用,我們可以把它用于我們的?tape_recorder?類或者別的地方。與前面的使用一個基類與多個具體派生類(在那里我們使用指針來實現多態的行為)的方法相比,還有一個額外的優點就是,它更容易管理生存期問題,我們不再需要刪除命令對象,它們可以按值傳遞和保存。我們在布爾上下文中使用?function f_?來測試命令是否可用。如果函數不包含一個目標,即一個函數或函數對象,它將返回?false, 這意味著我們不能調用它。這個測試在?execute?的實現中進行。以下是使用我們這個新類的一個例子:
[cpp] view plaincopy為了創建一個具體的命令,我們使用 Boost.Bind 來創建函數對象,當通過這些對象的調用操作符進行調用時,就會調用正確的?tape_recorder?成員函數。這些函數對象是自完備的;它們無參函數對象,即它們可以直接調用,無須傳入參數,這正是?boost::function<void()>?所表示的。換言之,以下代碼片斷創建了一個函數對象,它在配置好的?tape_recorder?實例上調用成員函數 play 。
[cpp] view plaincopy通常,我們不能保存?bind?所返回的函數對象,但由于 Boost.Function 兼容于任何函數對象,所以它可以。
[cpp] view plaincopy注意,這個類也支持調用?record, 它帶有一個類型為?const std::string&?的參數,這是由于成員函數set_function. 因為這個函數對象必須是無參的,所以我們需要綁定上下文以便?record?仍舊能夠獲得它的參數。當然,這是綁定器的工作。因而,在調用?record?之前,我們創建一個包含被錄音的字符串的函數對象。
[cpp] view plaincopy執行這個保存在?record?的函數對象,將在?tape_recorder?實例?tr?上執行?tape_recorder::record,并傳入字符串。有了 Boost.Function 和 Boost.Bind, 就可以實現解耦,讓調用代碼對于被調用代碼一無所知。以這種方式結合使用這兩個庫非常有用。你已經在這個?command?類中看到了,現在我們該清理一下了。由于 Boost.Function 的杰出功能,你所需的只是以下代碼:
[cpp] view plaincopy與 Boost.Function 一起使用 Boost.Lambda
與 Boost.Function 兼容于由 Boost.Bind 創建的函數對象一樣,它也支持由 Boost.Lambda 創建的函數對象。你用 Lambda 庫創建的任何函數對象都兼容于相應的?boost::function. 我們在前一節已經討論了基于綁定的一些內容,使用 Boost.Lambda 的主要不同之處是它能做得更多。我們可以輕易地創建一些小的、無名的函數,并把它們保存在?boost::function?實例中以用于后續的調用。我們已經在前一章中討論了 lambda 表達式,在那一章的所有例子中所創建的函數對象都可以保存在一個?function?實例中。function?與創建函數對象的庫的結合使用會非常強大。
代價的考慮
有一句諺語說,世界上沒有免費的午餐,對于 Boost.Function 來說也是如此。與使用函數指針相比,使用 Boost.Function 也有一些缺點,特別是對象大小的增加。顯然,一個函數指針只占用一個函數指針的空間大小(這當然了!),而一個?boost::function實例占的空間有三倍大。如果需要大量的回調函數,這可能會成為一個問題。函數指針在調用時的效率也稍高一些,因為函數指針是被直接調用的,而 Boost.Function 可能需要使用兩次函數指針的調用。最后,可能在某些需要與C庫保持后向兼容的情形下,只能使用函數指針。
雖然 Boost.Function 可能存在這些缺點,但是通常它們都不是什么實際問題。額外增加的大小非常小,而且(可能存在的)額外的函數指針調用所帶來的代價與真正執行目標函數所花費的時間相比通常都是非常小的。要求使用函數而不能使用 Boost.Function 的情形非常罕見。使用這個庫所帶來的巨大優點及靈活性顯然超出這些代價。
幕后的細節
至少了解一下這個庫如何工作的基礎知識是非常值得的。我們來看一下保存并調用一個函數指針、一個成員函數指針和一個函數對象這三種情形。這三種情形是不同的。要真正看到 Boost.Function 如何工作,只有看源代碼——不過我們的做法有些不同,我們試著搞清楚這些不同的版本究竟在處理方法上有些什么不同。我們也有一個不同要求的類,即當調用一個成員函數時,必須傳遞一個實例的指針給?function1?(這是我們的類的名字)的構造函數。function1?支持只有一個參數的函數。與 Boost.Function 相比一個較為寬松的投條件是,即使是對于成員函數,也只需要提供返回類型和參數類型。這個要求的直接結果就是,構造函數必須被傳入一個類的實例用于成員函數的調用(類型可以自動推斷)。
我們將要采用的方法是,創建一個泛型基類,它聲明了一個虛擬的調用操作符函數;然后,從這個基類派生三個類,分別支持三種不同形式的函數調用。這些類負責所有的工作,而另一個類,function1, 依據其構造函數的參數來決定實例化哪一個具體類。以下是調用器的基類,invoker_base.
[cpp] view plaincopy接著,我們開始定義?function_ptr_invoker, 它是一個具體調用器,公有派生自?invoker_base. 它的目的是調用普通函數。這個類也接受兩個類型,即返回類型和參數類型,它們被用于構造函數,構造函數接受一個函數指針作為參數。
[cpp] view plaincopy這個類模板可用于調用任意一個接受一個參數的普通函數。調用操作符簡單地以給定的參數調用保存在?func_?中的函數。請注意(的確有些奇怪)聲明一個保存函數指針的變量的那行代碼。
[cpp] view plaincopy你也可以用一個?typedef?來讓它好讀一些。
[cpp] view plaincopy接著,我們需要一個可以處理成員函數調用的類模板。記住,它要求在構造時給出一個類實例的指針,這一點與 Boost.Function 的做法不一樣。這樣可以節省我們的打字,因為是編譯器而不是程序員來推導這個類。
[cpp] view plaincopy這個類模板與普通函數指針的那個版本很相似。它與前一個版本的不同在于,構造函數保存了一個成員函數指針與一個對象指針,而調用操作符則在該對象(t_)上調用該成員函數(func_)。
最后,我們需要一個兼容函數對象的版本。這是所有實現中最容易的一個,至少在我們的方法中是這樣。通過使用單個模板參數,我們只表明類型?T?必須是一個真正的函數對象,因為我們想要調用它。說得夠多了。
[cpp] view plaincopy現在我們已經有了這些適用的積木,剩下來的就是把它們放在一起組成我們的自己的?boost::function, 即function1?類。我們想要一種辦法來發現要實例化哪一個調用器。然后我們可以把它存入一個?invoker_base?指針。這里的竊門就是,提供一些構造函數,它們有能力去檢查對于給出的參數,哪種調用器是正確的。這僅僅是重載而已,用了一點點手法,包括泛化兩個構造函數。
[cpp] view plaincopy如你所見,這里面最難的部分是正確地定義出推導系統以支持函數指針、類成員函數以及函數對象。無論使用何種設計來實現這類功能的庫,這都是必須的。最后,給出一些例子來測試我們這個方案。
[cpp] view plaincopy我們的?function1?類可以接受以下所有函數。
[cpp] view plaincopy它也可以使用象 Boost.Bind 和 Boost.Lambda 這樣的 binder 庫所返回的函數對象。我們的類與 Boost.Function 中的類相比要簡單多了,但是也已經足以看出創建和使用這樣一個庫的問題以及相關解決方法。知道一點關于一個庫是如何實現的事情,對于有效使用這個庫是非常有用的。
總結
以上是生活随笔為你收集整理的【Boost】boost库中function和bind一起使用的技巧(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Boost】boost库中functi
- 下一篇: PostgreSQL中表名、字段名大小写