【Boost】boost库中function的用法
要開始使用 Boost.Function, 就要包含頭文件?"boost/function.hpp", 或者某個帶數字的版本,從"boost/function/function0.hpp"?到?"boost/function/function10.hpp". 如果你知道你想保存在?function?中的函數的參數數量,這樣做可以讓編譯器僅包含需要的頭文件。如果包含?"boost/function.hpp", 那么就會把其它的頭文件也包含進去。
理解被存函數的最佳方法是把它想象為一個普通的函數對象,該函數對象用于封裝另一個函數(或函數對象)。這個被存的函數的最大用途是它可以被多次調用,而無須在創建?function?時立即使用。在聲明?functions 時,聲明中最重要的部分是函數的簽名。這部分即是告訴?function?它將保存的函數或函數對象的簽名和返回類型。我們已經看到,有兩種方法來執行這個聲明。這里有一個完整的程序,程序聲明了一個?boost::function?,它可以保存返回bool?(或某個可以隱式轉換為?bool?的類型)并接受兩個參數的類函數實體,第一個參數可以轉換為?int, 第二個參數可以轉換為?double.
[cpp] view plain copy當?function f?首次創建時,它不保存任何函數。它是空的,可以在一個布爾上下文中進行測試。如果你試圖調用一個沒有保存任何函數或函數對象的?function?,它將拋出一個類型?bad_function_call?的異常。為了避免這個問題,我們用普通的賦值語法把一個指向?some_func?的指針賦值給?f?。這導致?f?保存了到?some_func?的指針。最后,我們用參數10 (一個?int) 和 1.1 (一個?double)來調用?f?(用函數調用操作符)。要調用一個?function, 你必須提供被存函數或函數對象所期望的準確數量的參數。
回調的基礎
我們先來看看在沒有 Boost.Function 以前我們如何實現一個簡單的回調,然后再把代碼改為使用?function, 并看看會帶來什么優勢。我們從一個支持某種簡單的回調形式的類開始,它可以向任何對新值關注的對象報告值的改變。這里的回調是一種傳統的C風格回調,即使用普通函數。這種回調用可用于象GUI控制這樣的場合,它可以通知觀察者用戶改變了它的值,而不需要對監聽該信息的客戶有任何特殊的知識。
[cpp] view plaincopy這里的兩個函數,print_new_value?和?interested_in_the_change, 它們的函數簽名都兼容于?notifier?類的要求。這些函數指針被保存在一個?vector?內,并且無論何時它的值被改變,這些函數都會在一個循環里被調用。調用這些函數的一種語法是:
(*vec_[i])(value_);值(value_)被傳遞給解引用的函數指針(即?vec_[i]?所返回的)。另一種寫法也是有效的,即這樣:
vec_[i](value_);這種寫法看起來更好看些,但更為重要的是,它還可以允許你把函數指針更換為 Boost.Function 而沒有改變調用的語法。現在,工作還是正常的,但是,唉,函數對象不能用于這個?notifier?類。事實上,除了函數指針以外,別的任何東西都不能用,這的確是一種局限。但是,如果我們使用 Boost.Function,它就可以工作。重寫這個?notifier類非常容易。
[cpp] view plaincopy首先要做的事是,把?typedef?改為代表?boost::function?而不是函數指針。之前,我們定義的是一個函數指針;現在,我們使用泛型方法,很快就會看到它的用途。接著,我們把成員函數?add_observer?的簽名改為泛化的參數類型。我們也可以把它改為接受一個?boost::function,但那樣會要求該類的用戶必須也知道?function?的使用方法[2],而不是僅僅知道這個觀察者類型的要求就行了。應該注意到?add_observer?的這種變化并不應該是轉向function?的結果;無論如何代碼應該可以繼續工作。我們把它改為泛型的;現在,不管是函數指針、函數對象,還是?boost::function?實例都可以被傳遞給?add_observer, 而無須對已有用戶代碼進行任何改動。把元素加入到vector?的代碼有一些修改,現在需要創建一個?boost::function<void(int)>?實例。最后,我們把調用這些函數的語法改為可以使用函數、函數對象以及?boost::function?實例[3]。這種對不同類型的類似函數的"東西"的擴展支持可以立即用于帶狀態的函數對象,它們可以實現一些用函數很難做到的事情。
[2]?他們應該知道 Boost.Function,但如果他們不知道呢?我們添加到接口上的任何東西都必須及時向用戶解釋清楚。
[3]?現在我們知道,一開始我們就應該用這種語法。
[cpp] view plaincopy這個函數對象保存以前的值,并在值被改變時把舊值輸出到?std::cout?。注意,當它第一次被調用時,它并不知道舊值。這個函數對象在函數中使用一個靜態?bool?變量來檢查這一點,該變量被初始化為?true. 由于函數中的靜態變量是在函數第一次被調用時進行初始化的,所以它僅在第一次調用時被設為?true?。雖然也可以在普通函數中使用靜態變量來提供狀態,但是我們必須知道那樣不太好,而且很難做到多線程安全。因此,帶狀態的函數對象總是優于帶靜態變量的普通函數。notifier?類并不關心這是不是函數對象,只要符合要求就可以接受。以下更新的例子示范了它如何使用。
[cpp] view plaincopy關鍵一點要注意的是,我們新增的一個觀察者不是函數指針,而是一個?knows_the_previous_value?函數對象的實例。運行這段程序的輸出如下:
The value has been updated and is now 42 Ah, the value has changed. This is the first change of value, so I don't know the previous one.The value has been updated and is now 30 Ah, the value has changed. Previous value was 42在這里最大的優點不是放寬了對函數的要求(或者說,增加了對函數對象的支持),而是我們可以使用帶狀態的對象,這是非常需要的。我們對?notifier?類所做的修改非常簡單,而且用戶代碼不受影響。如上所示,把 Boost.Function 引入一個已有的設計中是非常容易的。
類成員函數
Boost.Function 不支持參數綁定,這在每次調用一個?function?就要調用同一個類實例的成員函數時是需要的。幸運的是,如果這個類實例被傳遞給?function?的話,我們就可以直接調用它的成員函數。這個?function?的簽名必須包含類的類型以及成員函數的簽名。換言之,顯式傳入的類實例要作為隱式的第一個參數,this。這樣就得到了一個在給出的對象上調用成員函數的函數對象。看一下以下這個類:
[cpp] view plaincopy成員函數?do_stuff?要從一個?boost::function?實例里被調用。要做到這一點,我們需要 function 接受一個some_class?實例,簽名的其它部分為一個?void?返回以及一個?int?參數。對于如何把?some_class?實例傳給 function,我們有三種選擇:傳值,傳引用,或者傳址。如何要傳值,代碼就應該這樣寫[4]
[4]?很少會有理由來以傳值的方式傳遞對象參數。
[cpp] view plaincopy注意,返回類型仍舊在最開始,后跟成員函數所在的類,最后是成員函數的參數類型。它就象傳遞一個?this?給一個函數,該函數暗地里用類實例調用一個非成員函數。要把函數?f?配置為成員函數?do_stuff, 然后調用它,我們這樣寫:
[cpp] view plaincopy如果要傳引用,我們要改一下函數的簽名,并傳遞一個?some_class?實例。
[cpp] view plaincopy最后,如果要傳?some_class?的指針[5],我們就要這樣寫:
[5]?裸指針或智能指針皆可。
[cpp]?view plaincopy好了,所有這些傳遞"虛擬?this"實例的方法都已經在庫中提供。當然,這種技術也是有限制的:你必須顯式地傳遞類實例;而理想上,你更愿意這個實例被綁定在函數中。乍一看,這似乎是 Boost.Function 的缺點,但有別的庫可以支持參數的綁定,如 Boost.Bind 和 Boost.Lambda. 我們將在本章稍后的地方示范這些庫會給 Boost.Function 帶有什么好處。
帶狀態的函數對象
我們已經看到,由于支持了函數對象,就可以給回調函數增加狀態。考慮這樣一個類,keeping_state, 它是一個帶狀態的函數對象。keeping_state?的實例記錄一個總和,它在每次調用操作符執行時被增加。現在,將該類的一個實例用于兩個?boost::function?實例,結果有些出人意外。
[cpp] view plaincopy寫完這段代碼并接著執行它,程序員可能期望保存在?ks?的總和是20,但不是;事實上,總和為0。以下是這段程序的運行結果。
The current total is 10 The current total is 10 After adding 10 two times, the total is 0原因是每一個?function?實例(f1?和?f2)都含有一個?ks?的拷貝,這兩個實例得到的總和都是10,但?ks?沒有變化。這可能是也可能不是你想要的,但是記住,boost::function?的缺省行為是復制它要調用的函數對象,這一點很重要。如果這導致不正確的語義,或者如果某些函數對象的復制代價太高,你就必須把函數對象包裝在boost::reference_wrapper?中,那樣?boost::function?的復制就會是一個?boost::reference_wrapper?的拷貝,它恰好持有一個到原始函數對象的引用。你無須直接使用?boost::reference_wrapper?,你可以使用另兩個助手函數,ref?和?cref。 這兩函數返回一個持有到某特定類型的引用或?const?引用的?reference_wrapper。在前例中,要獲得我們想要的語義,即使用同一個?keeping_state?實例,我們就需要把代碼修改如下:
[cpp] view plaincopyboost::ref?的用途是通知?boost::function,我們想保存一個到函數對象的引用,而不是一個拷貝。運行這個程序有以下輸出:
The current total is 10 The current total is 20 After adding 10 two times, the total is 20這正是我們想要的結果。使用?boost::ref?和?boost::cref?的不同之處就象引用與?const?引用的差異,對于后者,你只能調用其中的常量成員函數。以下例子使用一個名為?something_else?的函數對象,它有一個?const?的調用操作符。
[cpp] view plaincopy對于這個函數對象,我們可以使用?boost::ref?或?boost::cref.
[cpp] view plaincopy如果我們改變了?something_else?的實現,使其函數為非const, 則只有?boost::ref?可以使用,而?boost::cref?將導致一個編譯期錯誤。
[cpp] view plaincopy如果一個?function?包含一個被?boost::reference_wrapper?所包裝的函數對象,那么復制構造函數與賦值操作就會復制該引用,即?function?的拷貝將引向原先的函數對象。
[cpp] view plaincopy這等同于使用?boost::ref?并把函數對象?ks?賦給每一個 function 實例。
給回調函數增加狀態,可以發揮巨大的能力,這也正是使用 Boost.Function 與使用函數對象相比具有的非常突出的優點。
總結
以上是生活随笔為你收集整理的【Boost】boost库中function的用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PostgreSQL COPY 导入/导
- 下一篇: 【Boost】boost库中bind的用