## 用 法
要開始使用 Boost.Function, 就要包含頭文件 `"boost/function.hpp"`, 或者某個帶數字的版本,從 `"boost/function/function0.hpp"` 到 `"boost/function/function10.hpp"`. 如果你知道你想保存在 `function` 中的函數的參數數量,這樣做可以讓編譯器僅包含需要的頭文件。如果包含 `"boost/function.hpp"`, 那么就會把其它的頭文件也包含進去。
理解被存函數的最佳方法是把它想象為一個普通的函數對象,該函數對象用于封裝另一個函數(或函數對象)。這個被存的函數的最大用途是它可以被多次調用,而無須在創建 `function` 時立即使用。在聲明 `function`s 時,聲明中最重要的部分是函數的簽名。這部分即是告訴 `function` 它將保存的函數或函數對象的簽名和返回類型。我們已經看到,有兩種方法來執行這個聲明。這里有一個完整的程序,程序聲明了一個 `boost::function` ,它可以保存返回 `bool` (或某個可以隱式轉換為 `bool` 的類型)并接受兩個參數的類函數實體,第一個參數可以轉換為 `int`, 第二個參數可以轉換為 `double`.
```
#include <iostream>
#include "boost/function.hpp"
bool some_func(int i,double d) {
return i>d;
}
int main() {
boost::function<bool (int,double)> f;
f=&some_func;
f(10,1.1);
}
```
當 `function f` 首次創建時,它不保存任何函數。它是空的,可以在一個布爾上下文中進行測試。如果你試圖調用一個沒有保存任何函數或函數對象的 `function` ,它將拋出一個類型 `bad_function_call` 的異常。為了避免這個問題,我們用普通的賦值語法把一個指向 `some_func` 的指針賦值給 `f` 。這導致 `f` 保存了到 `some_func` 的指針。最后,我們用參數10 (一個 `int`) 和 1.1 (一個 `double`)來調用 `f` (用函數調用操作符)。要調用一個 `function`, 你必須提供被存函數或函數對象所期望的準確數量的參數。
### 回調的基礎
我們先來看看在沒有 Boost.Function 以前我們如何實現一個簡單的回調,然后再把代碼改為使用 `function`, 并看看會帶來什么優勢。我們從一個支持某種簡單的回調形式的類開始,它可以向任何對新值關注的對象報告值的改變。這里的回調是一種傳統的C風格回調,即使 用普通函數。這種回調用可用于象GUI控制這樣的場合,它可以通知觀察者用戶改變了它的值,而不需要對監聽該信息的客戶有任何特殊的知識。
```
#include <iostream>
#include <vector>
#include <algorithm>
#include "boost/function.hpp"
void print_new_value(int i) {
std::cout <<
"The value has been updated and is now " << i << '\n';
}
void interested_in_the_change(int i) {
std::cout << "Ah, the value has changed.\n";
}
class notifier {
typedef void (*function_type)(int);
std::vector<function_type> vec_;
int value_;
public:
void add_observer(function_type t) {
vec_.push_back(t);
}
void change_value(int i) {
value_=i;
for (std::size_t i=0;i<vec_.size();++i) {
(*vec_[i])(value_);
}
}
};
int main() {
notifier n;
n.add_observer(&print_new_value);
n.add_observer(&interested_in_the_change);
n.change_value(42);
}
```
這里的兩個函數,`print_new_value` 和 `interested_in_the_change`, 它們的函數簽名都兼容于 `notifier` 類的要求。這些函數指針被保存在一個 `vector` 內,并且無論何時它的值被改變,這些函數都會在一個循環里被調用。調用這些函數的一種語法是:
```
(*vec_[i])(value_);
```
值(`value_`)被傳遞給解引用的函數指針(即 `vec_[i]` 所返回的)。另一種寫法也是有效的,即這樣:
```
vec_[i](value_);
```
這種寫法看起來更好看些,但更為重要的是,它還可以允許你把函數指針更換為 Boost.Function 而沒有改變調用的語法。現在,工作還是正常的,但是,唉,函數對象不能用于這個 `notifier` 類。事實上,除了函數指針以外,別的任何東西都不能用,這的確是一種局限。但是,如果我們使用 Boost.Function,它就可以工作。重寫這個 `notifier` 類非常容易。
```
class notifier {
typedef boost::function<void(int)> function_type;
std::vector<function_type> vec_;
int value_;
public:
template <typename T> void add_observer(T t) {
vec_.push_back(function_type(t));
}
void change_value(int i) {
value_=i;
for (std::size_t i=0;i<vec_.size();++i) {
vec_[i](value_);
}
}
};
```
首先要做的事是,把 `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\] 現在我們知道,一開始我們就應該用這種語法。
```
class knows_the_previous_value {
int last_value_;
public:
void operator()(int i) {
static bool first_time=true;
if (first_time) {
last_value_=i;
std::cout <<
"This is the first change of value, \
so I don't know the previous one.\n";
first_time=false;
return;
}
std::cout << "Previous value was " << last_value_ << '\n';
last_value_=i;
}
};
```
這個函數對象保存以前的值,并在值被改變時把舊值輸出到 `std::cout` 。注意,當它第一次被調用時,它并不知道舊值。這個函數對象在函數中使用一個靜態 `bool` 變量來檢查這一點,該變量被初始化為 `true`. 由于函數中的靜態變量是在函數第一次被調用時進行初始化的,所以它僅在第一次調用時被設為 `true` 。雖然也可以在普通函數中使用靜態變量來提供狀態,但是我們必須知道那樣不太好,而且很難做到多線程安全。因此,帶狀態的函數對象總是優于帶靜態變量的普通函數。`notifier` 類并不關心這是不是函數對象,只要符合要求就可以接受。以下更新的例子示范了它如何使用。
```
int main() {
notifier n;
n.add_observer(&print_new_value);
n.add_observer(&interested_in_the_change);
n.add_observer(knows_the_previous_value());
n.change_value(42);
std::cout << '\n';
n.change_value(30);
}
```
關鍵一點要注意的是,我們新增的一個觀察者不是函數指針,而是一個 `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`。這樣就得到了一個在給出的對象上調用成員函數的函數對象。看一下以下這個類:
```
class some_class {
public:
void do_stuff(int i) const {
std::cout << "OK. Stuff is done. " << i << '\n';
}
};
```
成員函數 `do_stuff` 要從一個 `boost::function` 實例里被調用。要做到這一點,我們需要 function 接受一個 `some_class` 實例,簽名的其它部分為一個 `void` 返回以及一個 `int` 參數。對于如何把 `some_class` 實例傳給 function,我們有三種選擇:傳值,傳引用,或者傳址。如何要傳值,代碼就應該這樣寫\[4\]
> \[4\] 很少會有理由來以傳值的方式傳遞對象參數。
```
boost::function<void(some_class,int)> f;
```
注意,返回類型仍舊在最開始,后跟成員函數所在的類,最后是成員函數的參數類型。它就象傳遞一個 `this` 給一個函數,該函數暗地里用類實例調用一個非成員函數。要把函數 `f` 配置為成員函數 `do_stuff`, 然后調用它,我們這樣寫:
```
f=&some_class::do_stuff;
f(some_class(),2);
```
如果要傳引用,我們要改一下函數的簽名,并傳遞一個 `some_class` 實例。
```
boost::function<void(some_class&,int)> f;
f=&some_class::do_stuff;
some_class s;
f(s,1);
```
最后,如果要傳 `some_class` 的指針\[5\],我們就要這樣寫:
> \[5\] 裸指針或智能指針皆可。
```
boost::function<void(some_class*,int)> f;
f=&some_class::do_stuff;
some_class s;
f(&s,3);
```
好了,所有這些傳遞"虛擬 `this`"實例的方法都已經在庫中提供。當然,這種技術也是有限制的:你必須顯式地傳遞類實例;而理想上,你更愿意這個實例被綁定在函數中。乍一看,這似乎是 Boost.Function 的缺點,但有別的庫可以支持參數的綁定,如 Boost.Bind 和 Boost.Lambda. 我們將在本章稍后的地方示范這些庫會給 Boost.Function 帶有什么好處。
### 帶狀態的函數對象
我們已經看到,由于支持了函數對象,就可以給回調函數增加狀態。考慮這樣一個類,`keeping_state`, 它是一個帶狀態的函數對象。`keeping_state` 的實例記錄一個總和,它在每次調用操作符執行時被增加。現在,將該類的一個實例用于兩個 `boost::function` 實例,結果有些出人意外。
```
#include <iostream>
#include "boost/function.hpp"
class keeping_state {
int total_;
public:
keeping_state():total_(0) {}
int operator()(int i) {
total_+=i;
return total_;
}
int total() const {
return total_;
}
};
int main() {
keeping_state ks;
boost::function<int(int)> f1;
f1=ks;
boost::function<int(int)> f2;
f2=ks;
std::cout << "The current total is " << f1(10) << '\n';
std::cout << "The current total is " << f2(10) << '\n';
std::cout << "After adding 10 two times, the total is "
<< ks.total() << '\n';
}
```
寫完這段代碼并接著執行它,程序員可能期望保存在 `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` 實例,我們就需要把代碼修改如下:
```
int main() {
keeping_state ks;
boost::function<int(int)> f1;
f1=boost::ref(ks);
boost::function<int(int)> f2;
f2=boost::ref(ks);
std::cout << "The current total is " << f1(10) << '\n';
std::cout << "The current total is " << f2(10) << '\n';
std::cout << "After adding 10 two times, the total is "
<< ks.total() << '\n';
}
```
`boost::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` 的調用操作符。
```
class something_else {
public:
void operator()() const {
std::cout << "This works with boost::cref\n";
}
};
```
對于這個函數對象,我們可以使用 `boost::ref` 或 `boost::cref`.
```
something_else s;
boost::function0<void> f1;
f1=boost::ref(s);
f1();
boost::function0<void> f2;
f2=boost::cref(s);
f2();
```
如果我們改變了 `something_else` 的實現,使其函數為非`const`, 則只有 `boost::ref` 可以使用,而 `boost::cref` 將導致一個編譯期錯誤。
```
class something_else {
public:
void operator()() {
std::cout <<
"This works only with boost::ref, or copies\n";
}
};
something_else s;
boost::function0<void> f1;
f1=boost::ref(s); // This still works
f1();
boost::function0<void> f2;
f2=boost::cref(s); // This doesn't work;
// the function call operator is not const
f2();
```
如果一個 `function` 包含一個被 `boost::reference_wrapper` 所包裝的函數對象,那么復制構造函數與賦值操作就會復制該引用,即 `function` 的拷貝將引向原先的函數對象。
```
int main() {
keeping_state ks;
boost::function1<int,int> f1; // 譯注:原文為boost::function<int,int> f1,有誤
f1=boost::ref(ks);
boost::function1<int,int> f2(f1); // 譯注:原文為boost::function<int,int> f2(f1),有誤
boost::function1<short,short> f3; // 譯注:原文為boost::function<short,short> f3,有誤
f3=f1;
std::cout << "The current total is " << f1(10) << '\n';
std::cout << "The current total is " << f2(10) << '\n';
std::cout << "The current total is " << f3(10) << '\n';
std::cout << "After adding 10 three times, the total is "
<< ks.total() << '\n';
}
```
這等同于使用 `boost::ref` 并把函數對象 `ks` 賦給每一個 function 實例。
給回調函數增加狀態,可以發揮巨大的能力,這也正是使用 Boost.Function 與使用函數對象相比具有的非常突出的優點。
### 與 Boost.Function 一起使用 Boost.Bind??
當我們把 Boost.Function 與某個支持參數綁定的庫結合起來使用時,事情變得更為有趣。Boost.Bind 為普通函數、成員函數以及成員變量提供參數綁定。這非常適合于 Boost.Function, 我們常常需要這類綁定,由于我們使用的類本身并不是函數對象。那么,我們用 Boost.Bind 把它們轉變為函數對象,然后我們可以用 Boost.Function 來保存它們并稍后調用。在將圖形用戶界面(GUIs)與如何響應用戶的操作進行分離時,幾乎總是要使用某種回調方法。如果這種回調機制是基于函數指針的, 就很難避免對可以使用回調的類型的某些限制,也就增加了界面表現與業務邏輯之間的耦合風險。通過使用 Boost.Function,我們可以避免這些事情,并且當與某個支持參數綁定的庫結合使用時,我們可以輕而易舉地把上下文提供給調用的函數。這是本庫 最常見的用途之一,把業務邏輯即從表示層分離出來。
以下例子包含一個藝術級的磁帶錄音機,定義如下:
```
class tape_recorder {
public:
void play() {
std::cout << "Since my baby left me...\n";
}
void stop() {
std::cout << "OK, taking a break\n";
}
void forward() {
std::cout << "whizzz\n";
}
void rewind() {
std::cout << "zzzihw\n";
}
void record(const std::string& sound) {
std::cout << "Recorded: " << sound << '\n';
}
};
```
這個磁帶錄音機可以從一個GUI進行控制,或者也可能從一個腳本客戶端進行控制,或者從別的源進行控 制,這意味著我們不想把這些函數的執行與它們的實現耦合起來。建立這種分離的一個常用的方法是,用專門的對象負責執行命令,而讓客戶對命令如何執行毫無所 知。這也被稱為命令模式(Command pattern),并且在它非常有用。這種模式的特定實現中的一個問題是,需要為每個命令創建單獨的類。以下片斷示范了它看起來是個什么樣子:
```
class command_base {
public:
virtual bool enabled() const=0;
virtual void execute()=0;
virtual ~command_base() {}
};
class play_command : public command_base {
tape_recorder* p_;
public:
play_command(tape_recorder* p):p_(p) {}
bool enabled() const {
return true;
}
void execute() {
p_->play();
}
};
class stop_command : public command_base {
tape_recorder* p_;
public:
stop_command(tape_recorder* p):p_(p) {}
bool enabled() const {
return true;
}
void execute() {
p_->stop();
}
};
```
這并不是一個非常吸引的方案,因為它使得代碼膨脹,有許多簡單的命令類,而它們只是簡單地負責調用一個對象的單個成員函數。有時候,這是必需的,因為這些命令可能需要實現業務邏輯和調用函數,但通常它只是由于我們所使用的工具有所限制而已。這些命令類可以這樣使用:
```
int main() {
tape_recorder tr;
// 使用命令模式
command_base* pPlay=new play_command(&tr);
command_base* pStop=new stop_command(&tr);
// 在按下某個按鈕時調用
pPlay->execute();
pStop->execute();
delete pPlay;
delete pStop;
}
```
現在,不用再創建額外的具體的命令類,如果我們實現的命令都是調用一個返回 `void` 且沒有參數(先暫時忽略函數 record, 它帶有一個參數)的成員函數的話,我們可以來點泛化。不用再創建一組具體的命令,我們可以在類中保存一個指向正確成員函數的指針。這是邁向正確方向\[6\]的一大步,就象這樣:
> \[6\] 雖然損失了一點效率。
```
class tape_recorder_command : public command_base {
void (tape_recorder::*func_)();
tape_recorder* p_;
public:
tape_recorder_command(
tape_recorder* p,
void (tape_recorder::*func)()) : p_(p),func_(func) {}
bool enabled() const {
return true;
}
void execute() {
(p_->*func_)();
}
};
```
這個命令模式的實現要好多了,因為它不需要我們再創建一組完成相同事情的獨立的類。這里的不同在于我們保存了一個 `tape_recorder` 成員函數指針在 `func_` 中,它要在構造函數中提供。命令的執行部分可能并不是你要展現給你的朋友看的東西,因為成員指針操作符對于一些人來說可能還不太熟悉。但是,這可以被看為一個低層的實現細節,所以還算好。有了這個類,我們可以進行泛化處理,不再需要實現單獨的命令類。
```
int main() {
tape_recorder tr;
// 使用改進的命令模式
command_base* pPlay=
new tape_recorder_command(&tr,&tape_recorder::play);
command_base* pStop=
new tape_recorder_command(&tr,&tape_recorder::stop);
// 從一個GUI或一個腳本客戶端進行調用
pPlay->execute();
pStop->execute();
delete pPlay;
delete pStop;
}
```
你可能還沒有理解,我們已經在開始實現一個簡單的 `boost::function` 版本,它已經可以做到我們想要的。不要重復發明輪子,讓我們重點關注手邊的工作:分離調用與實現。以下是一個全新實現的 `command` 類,它更容易編寫、維護以及理解。
```
class command {
boost::function<void()> f_;
public:
command() {}
command(boost::function<void()> f):f_(f) {}
void execute() {
if (f_) {
f_();
}
}
template <typename Func> void set_function(Func f) {
f_=f;
}
bool enabled() const {
return f_;
}
};
```
通過使用 Boost.Function,我們可以立即從同時兼容函數和函數對象——包括由綁定器生成的函數對象——的靈活性之中獲益。這個 `command` 類把函數保存在一個返回 `void` 且不接受參數的 `boost::function` 中。為了讓這個類更加靈活,我們提供了在運行期修改函數對象的方法,使用一個泛型的成員函數,`set_function`.
```
template <typename Func> void set_function(Func f) {
f_=f;
}
```
通過使用泛型方法,任何函數、函數對象,或者綁定器都兼容于我們的 `command` 類。我們也可以選擇把 `boost:: function` 作為參數,并使用 `function` 的轉型構造函數來達到同樣的效果。這個 `command` 類非常通用,我們可以把它用于我們的 `tape_recorder` 類或者別的地方。與前面的使用一個基類與多個具體派生類(在那里我們使用指針來實現多態的行為)的方法相比,還有一個額外的優點就是,它更容易管理生存期問題,我們不再需要刪除命令對象,它們可以按值傳遞和保存。我們在布爾上下文中使用 `function f_` 來測試命令是否可用。如果函數不包含一個目標,即一個函數或函數對象,它將返回 `false`, 這意味著我們不能調用它。這個測試在 `execute` 的實現中進行。以下是使用我們這個新類的一個例子:
```
int main() {
tape_recorder tr;
command play(boost::bind(&tape_recorder::play,&tr));
command stop(boost::bind(&tape_recorder::stop,&tr));
command forward(boost::bind(&tape_recorder::stop,&tr));
command rewind(boost::bind(&tape_recorder::rewind,&tr));
command record;
// 從某些GUI控制中調用...
if (play.enabled()) {
play.execute();
}
// 從某些腳本客戶端調用...
stop.execute();
// Some inspired songwriter has passed some lyrics
std::string s="What a beautiful morning...";
record.set_function(
boost::bind(&tape_recorder::record,&tr,s));
record.execute();
}
```
為了創建一個具體的命令,我們使用 Boost.Bind 來創建函數對象,當通過這些對象的調用操作符進行調用時,就會調用正確的 `tape_recorder` 成員函數。這些函數對象是自完備的;它們無參函數對象,即它們可以直接調用,無須傳入參數,這正是 `boost::function<void()>` 所表示的。換言之,以下代碼片斷創建了一個函數對象,它在配置好的 `tape_recorder` 實例上調用成員函數 play 。
```
boost::bind(&tape_recorder::play,&tr)
```
通常,我們不能保存 `bind` 所返回的函數對象,但由于 Boost.Function 兼容于任何函數對象,所以它可以。
```
boost::function<void()> f(boost::bind(&tape_recorder::play,&tr));
```
注意,這個類也支持調用 `record`, 它帶有一個類型為 `const std::string&` 的參數,這是由于成員函數 `set_function`. 因為這個函數對象必須是無參的,所以我們需要綁定上下文以便 `record` 仍舊能夠獲得它的參數。當然,這是綁定器的工作。因而,在調用 `record` 之前,我們創建一個包含被錄音的字符串的函數對象。
```
std::string s="What a beautiful morning...";
record.set_function(
boost::bind(&tape_recorder::record,&tr,s));
```
執行這個保存在 `record` 的函數對象,將在 `tape_recorder` 實例 `tr` 上執行 `tape_recorder::record`,并傳入字符串。有了 Boost.Function 和 Boost.Bind, 就可以實現解耦,讓調用代碼對于被調用代碼一無所知。以這種方式結合使用這兩個庫非常有用。你已經在這個 `command` 類中看到了,現在我們該清理一下了。由于 Boost.Function 的杰出功能,你所需的只是以下代碼:
```
typedef boost::function<void()> command;
```
### 與 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`.
```
template <typename R, typename Arg> class invoker_base {
public:
virtual R operator()(Arg arg)=0;
};
```
接著,我們開始定義 `function_ptr_invoker`, 它是一個具體調用器,公有派生自 `invoker_base`. 它的目的是調用普通函數。這個類也接受兩個類型,即返回類型和參數類型,它們被用于構造函數,構造函數接受一個函數指針作為參數。
```
template <typename R, typename Arg> class function_ptr_invoker
: public invoker_base<R,Arg> {
R (*func_)(Arg);
public:
function_ptr_invoker(R (*func)(Arg)):func_(func) {}
R operator()(Arg arg) {
return (func_)(arg);
}
};
```
這個類模板可用于調用任意一個接受一個參數的普通函數。調用操作符簡單地以給定的參數調用保存在 `func_` 中的函數。請注意(的確有些奇怪)聲明一個保存函數指針的變量的那行代碼。
```
R (*func_)(Arg);
```
你也可以用一個 `typedef` 來讓它好讀一些。
```
typedef R (*FunctionT)(Arg);
FunctionT func_;
```
接著,我們需要一個可以處理成員函數調用的類模板。記住,它要求在構造時給出一個類實例的指針,這一點與 Boost.Function 的做法不一樣。這樣可以節省我們的打字,因為是編譯器而不是程序員來推導這個類。
```
template <typename R, typename Arg, typename T>
class member_ptr_invoker :
public invoker_base<R,Arg> {
R (T::*func_)(Arg);
T* t_;
public:
member_ptr_invoker(R (T::*func)(Arg),T* t)
:func_(func),t_(t) {}
R operator()(Arg arg) {
return (t_->*func_)(arg);
}
};
```
這個類模板與普通函數指針的那個版本很相似。它與前一個版本的不同在于,構造函數保存了一個成員函數指針與一個對象指針,而調用操作符則在該對象(`t_`)上調用該成員函數(`func_`)。
最后,我們需要一個兼容函數對象的版本。這是所有實現中最容易的一個,至少在我們的方法中是這樣。通過使用單個模板參數,我們只表明類型 `T` 必須是一個真正的函數對象,因為我們想要調用它。說得夠多了。
```
template <typename R, typename Arg, typename T>
class function_object_invoker :
public invoker_base<R,Arg> {
T t_;
public:
function_object_invoker(T t):t_(t) {}
R operator()(Arg arg) {
return t_(arg);
}
};
```
現在我們已經有了這些適用的積木,剩下來的就是把它們放在一起組成我們的自己的 `boost::function`, 即 `function1` 類。我們想要一種辦法來發現要實例化哪一個調用器。然后我們可以把它存入一個 `invoker_base` 指針。這里的竊門就是,提供一些構造函數,它們有能力去檢查對于給出的參數,哪種調用器是正確的。這僅僅是重載而已,用了一點點手法,包括泛化兩個構造函數。
```
template <typename R, typename Arg> class function1 {
invoker_base<R,Arg>* invoker_;
public:
function1(R (*func)(Arg)) :
invoker_(new function_ptr_invoker<R,Arg>(func)) {}
template <typename T> function1(R (T::*func)(Arg),T* p) :
invoker_(new member_ptr_invoker<R,Arg,T>(func,p)) {}
template <typename T> function1(T t) :
invoker_(new function_object_invoker<R,Arg,T>(t)) {}
R operator()(Arg arg) {
return (*invoker_)(arg);
}
~function1() {
delete invoker_;
}
};
```
如你所見,這里面最難的部分是正確地定義出推導系統以支持函數指針、類成員函數以及函數對象。無論使用何種設計來實現這類功能的庫,這都是必須的。最后,給出一些例子來測試我們這個方案。
```
bool some_function(const std::string& s) {
std::cout << s << " This is really neat\n";
return true;
}
class some_class {
public:
bool some_function(const std::string& s) {
std::cout << s << " This is also quite nice\n";
return true;
}
};
class some_function_object {
public:
bool operator()(const std::string& s) {
std::cout << s <<
" This should work, too, in a flexible solution\n";
return true;
}
};
```
我們的 `function1` 類可以接受以下所有函數。
```
int main() {
function1<bool,const std::string&> f1(&some_function);
f1(std::string("Hello"));
some_class s;
function1<bool,const std::string&>
f2(&some_class::some_function,&s);
f2(std::string("Hello"));
function1<bool,const std::string&>
f3(boost::bind(&some_class::some_function,&s,_1));
f3(std::string("Hello"));
some_function_object fso;
function1<bool,const std::string&>
f4(fso);
f4(std::string("Hello"));
}
```
它也可以使用象 Boost.Bind 和 Boost.Lambda 這樣的 binder 庫所返回的函數對象。我們的類與 Boost.Function 中的類相比要簡單多了,但是也已經足以看出創建和使用這樣一個庫的問題以及相關解決方法。知道一點關于一個庫是如何實現的事情,對于有效使用這個庫是非常有用的。
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結