一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

boost::function用法詳解

 思考的軌跡 2012-04-23

boost::function用法詳解


要開始使用 Boost.Function, 就要包含頭文件 "boost/function.hpp", 或者某個帶數(shù)字的版本,從 "boost/function/function0.hpp""boost/function/function10.hpp". 如果你知道你想保存在 function 中的函數(shù)的參數(shù)數(shù)量,這樣做可以讓編譯器僅包含需要的頭文件。如果包含 "boost/function.hpp", 那么就會把其它的頭文件也包含進(jìn)去。

理解被存函數(shù)的最佳方法是把它想象為一個普通的函數(shù)對象,該函數(shù)對象用于封裝另一個函數(shù)(或函數(shù)對象)。這個被存的函數(shù)的最大用途是它可以被多次調(diào)用,而無須在創(chuàng)建 function 時立即使用。在聲明 functions 時,聲明中最重要的部分是函數(shù)的簽名。這部分即是告訴 function 它將保存的函數(shù)或函數(shù)對象的簽名和返回類型。我們已經(jīng)看到,有兩種方法來執(zhí)行這個聲明。這里有一個完整的程序,程序聲明了一個 boost::function ,它可以保存返回 bool (或某個可以隱式轉(zhuǎn)換為 bool 的類型)并接受兩個參數(shù)的類函數(shù)實體,第一個參數(shù)可以轉(zhuǎn)換為 int, 第二個參數(shù)可以轉(zhuǎn)換為 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);
}

當(dāng) function f 首次創(chuàng)建時,它不保存任何函數(shù)。它是空的,可以在一個布爾上下文中進(jìn)行測試。如果你試圖調(diào)用一個沒有保存任何函數(shù)或函數(shù)對象的 function ,它將拋出一個類型 bad_function_call 的異常。為了避免這個問題,我們用普通的賦值語法把一個指向 some_func 的指針賦值給 f 。這導(dǎo)致 f 保存了到 some_func 的指針。最后,我們用參數(shù)10 (一個 int) 和 1.1 (一個 double)來調(diào)用 f (用函數(shù)調(diào)用操作符)。要調(diào)用一個 function, 你必須提供被存函數(shù)或函數(shù)對象所期望的準(zhǔn)確數(shù)量的參數(shù)。

回調(diào)的基礎(chǔ)

我們先來看看在沒有 Boost.Function 以前我們?nèi)绾螌崿F(xiàn)一個簡單的回調(diào),然后再把代碼改為使用 function, 并看看會帶來什么優(yōu)勢。我們從一個支持某種簡單的回調(diào)形式的類開始,它可以向任何對新值關(guān)注的對象報告值的改變。這里的回調(diào)是一種傳統(tǒng)的C風(fēng)格回調(diào),即使用普通函數(shù)。這種回調(diào)用可用于象GUI控制這樣的場合,它可以通知觀察者用戶改變了它的值,而不需要對監(jiān)聽該信息的客戶有任何特殊的知識。

#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);
}

這里的兩個函數(shù),print_new_valueinterested_in_the_change, 它們的函數(shù)簽名都兼容于 notifier 類的要求。這些函數(shù)指針被保存在一個 vector 內(nèi),并且無論何時它的值被改變,這些函數(shù)都會在一個循環(huán)里被調(diào)用。調(diào)用這些函數(shù)的一種語法是:

(*vec_[i])(value_);

值(value_)被傳遞給解引用的函數(shù)指針(即 vec_[i] 所返回的)。另一種寫法也是有效的,即這樣:

vec_[i](value_);

這種寫法看起來更好看些,但更為重要的是,它還可以允許你把函數(shù)指針更換為 Boost.Function 而沒有改變調(diào)用的語法。現(xiàn)在,工作還是正常的,但是,唉,函數(shù)對象不能用于這個 notifier 類。事實上,除了函數(shù)指針以外,別的任何東西都不能用,這的確是一種局限。但是,如果我們使用 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 而不是函數(shù)指針。之前,我們定義的是一個函數(shù)指針;現(xiàn)在,我們使用泛型方法,很快就會看到它的用途。接著,我們把成員函數(shù) add_observer 的簽名改為泛化的參數(shù)類型。我們也可以把它改為接受一個 boost::function,但那樣會要求該類的用戶必須也知道 function 的使用方法[2],而不是僅僅知道這個觀察者類型的要求就行了。應(yīng)該注意到 add_observer 的這種變化并不應(yīng)該是轉(zhuǎn)向 function 的結(jié)果;無論如何代碼應(yīng)該可以繼續(xù)工作。我們把它改為泛型的;現(xiàn)在,不管是函數(shù)指針、函數(shù)對象,還是 boost::function 實例都可以被傳遞給 add_observer, 而無須對已有用戶代碼進(jìn)行任何改動。把元素加入到 vector 的代碼有一些修改,現(xiàn)在需要創(chuàng)建一個 boost::function<void(int)> 實例。最后,我們把調(diào)用這些函數(shù)的語法改為可以使用函數(shù)、函數(shù)對象以及 boost::function 實例

[2] 他們應(yīng)該知道 Boost.Function,但如果他們不知道呢?我們添加到接口上的任何東西都必須及時向用戶解釋清楚。

[3] 現(xiàn)在我們知道,一開始我們就應(yīng)該用這種語法。

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;
}
};

這個函數(shù)對象保存以前的值,并在值被改變時把舊值輸出到 std::cout 。注意,當(dāng)它第一次被調(diào)用時,它并不知道舊值。這個函數(shù)對象在函數(shù)中使用一個靜態(tài) bool 變量來檢查這一點(diǎn),該變量被初始化為 true. 由于函數(shù)中的靜態(tài)變量是在函數(shù)第一次被調(diào)用時進(jìn)行初始化的,所以它僅在第一次調(diào)用時被設(shè)為 true 。雖然也可以在普通函數(shù)中使用靜態(tài)變量來提供狀態(tài),但是我們必須知道那樣不太好,而且很難做到多線程安全。因此,帶狀態(tài)的函數(shù)對象總是優(yōu)于帶靜態(tài)變量的普通函數(shù)。notifier 類并不關(guān)心這是不是函數(shù)對象,只要符合要求就可以接受。以下更新的例子示范了它如何使用。

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);
}

關(guān)鍵一點(diǎn)要注意的是,我們新增的一個觀察者不是函數(shù)指針,而是一個 knows_the_previous_value 函數(shù)對象的實例。運(yùn)行這段程序的輸出如下:

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

在這里最大的優(yōu)點(diǎn)不是放寬了對函數(shù)的要求(或者說,增加了對函數(shù)對象的支持),而是我們可以使用帶狀態(tài)的對象,這是非常需要的。我們對 notifier 類所做的修改非常簡單,而且用戶代碼不受影響。如上所示,把 Boost.Function 引入一個已有的設(shè)計中是非常容易的。

類成員函數(shù)

Boost.Function 不支持參數(shù)綁定,這在每次調(diào)用一個 function 就要調(diào)用同一個類實例的成員函數(shù)時是需要的。幸運(yùn)的是,如果這個類實例被傳遞給 function 的話,我們就可以直接調(diào)用它的成員函數(shù)。這個 function 的簽名必須包含類的類型以及成員函數(shù)的簽名。換言之,顯式傳入的類實例要作為隱式的第一個參數(shù),this。這樣就得到了一個在給出的對象上調(diào)用成員函數(shù)的函數(shù)對象??匆幌乱韵逻@個類:

class some_class {
public:
void do_stuff(int i) const {
std::cout << "OK. Stuff is done. " << i << '/n';
}
};

成員函數(shù) do_stuff 要從一個 boost::function 實例里被調(diào)用。要做到這一點(diǎn),我們需要 function 接受一個 some_class 實例,簽名的其它部分為一個 void 返回以及一個 int 參數(shù)。對于如何把 some_class 實例傳給 function,我們有三種選擇:傳值,傳引用,或者傳址。如何要傳值,代碼就應(yīng)該這樣寫[4]

[4] 很少會有理由來以傳值的方式傳遞對象參數(shù)。

boost::function<void(some_class,int)> f;

注意,返回類型仍舊在最開始,后跟成員函數(shù)所在的類,最后是成員函數(shù)的參數(shù)類型。它就象傳遞一個 this 給一個函數(shù),該函數(shù)暗地里用類實例調(diào)用一個非成員函數(shù)。要把函數(shù) f 配置為成員函數(shù) do_stuff, 然后調(diào)用它,我們這樣寫:

f=&some_class::do_stuff;
f(some_class(),2);

如果要傳引用,我們要改一下函數(shù)的簽名,并傳遞一個 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"實例的方法都已經(jīng)在庫中提供。當(dāng)然,這種技術(shù)也是有限制的:你必須顯式地傳遞類實例;而理想上,你更愿意這個實例被綁定在函數(shù)中。乍一看,這似乎是 Boost.Function 的缺點(diǎn),但有別的庫可以支持參數(shù)的綁定,如 Boost.Bind 和 Boost.Lambda. 我們將在本章稍后的地方示范這些庫會給 Boost.Function 帶有什么好處。

帶狀態(tài)的函數(shù)對象

我們已經(jīng)看到,由于支持了函數(shù)對象,就可以給回調(diào)函數(shù)增加狀態(tài)??紤]這樣一個類,keeping_state, 它是一個帶狀態(tài)的函數(shù)對象。keeping_state 的實例記錄一個總和,它在每次調(diào)用操作符執(zhí)行時被增加?,F(xiàn)在,將該類的一個實例用于兩個 boost::function 實例,結(jié)果有些出人意外。

#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';
}

寫完這段代碼并接著執(zhí)行它,程序員可能期望保存在 ks 的總和是20,但不是;事實上,總和為0。以下是這段程序的運(yùn)行結(jié)果。

The current total is 10
The current total is 10
After adding 10 two times, the total is 0

原因是每一個 function 實例(f1f2)都含有一個 ks 的拷貝,這兩個實例得到的總和都是10,但 ks 沒有變化。這可能是也可能不是你想要的,但是記住,boost::function 的缺省行為是復(fù)制它要調(diào)用的函數(shù)對象,這一點(diǎn)很重要。如果這導(dǎo)致不正確的語義,或者如果某些函數(shù)對象的復(fù)制代價太高,你就必須把函數(shù)對象包裝在 boost::reference_wrapper 中,那樣 boost::function 的復(fù)制就會是一個 boost::reference_wrapper 的拷貝,它恰好持有一個到原始函數(shù)對象的引用。你無須直接使用 boost::reference_wrapper ,你可以使用另兩個助手函數(shù),refcref。 這兩函數(shù)返回一個持有到某特定類型的引用或 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,我們想保存一個到函數(shù)對象的引用,而不是一個拷貝。運(yùn)行這個程序有以下輸出:

The current total is 10
The current total is 20
After adding 10 two times, the total is 20

這正是我們想要的結(jié)果。使用 boost::refboost::cref 的不同之處就象引用與 const 引用的差異,對于后者,你只能調(diào)用其中的常量成員函數(shù)。以下例子使用一個名為 something_else 的函數(shù)對象,它有一個 const 的調(diào)用操作符。

class something_else {
public:
void operator()() const {
std::cout << "This works with boost::cref/n";
}
};

對于這個函數(shù)對象,我們可以使用 boost::refboost::cref.

something_else s;
boost::function0<void> f1;
f1=boost::ref(s);
f1();
boost::function0<void> f2;
f2=boost::cref(s);
f2();

如果我們改變了 something_else 的實現(xiàn),使其函數(shù)為非const, 則只有 boost::ref 可以使用,而 boost::cref 將導(dǎo)致一個編譯期錯誤。

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 所包裝的函數(shù)對象,那么復(fù)制構(gòu)造函數(shù)與賦值操作就會復(fù)制該引用,即 function 的拷貝將引向原先的函數(shù)對象。

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 并把函數(shù)對象 ks 賦給每一個 function 實例。

給回調(diào)函數(shù)增加狀態(tài),可以發(fā)揮巨大的能力,這也正是使用 Boost.Function 與使用函數(shù)對象相比具有的非常突出的優(yōu)點(diǎn)。

與 Boost.Function 一起使用 Boost.Bind  

當(dāng)我們把 Boost.Function 與某個支持參數(shù)綁定的庫結(jié)合起來使用時,事情變得更為有趣。Boost.Bind 為普通函數(shù)、成員函數(shù)以及成員變量提供參數(shù)綁定。這非常適合于 Boost.Function, 我們常常需要這類綁定,由于我們使用的類本身并不是函數(shù)對象。那么,我們用 Boost.Bind 把它們轉(zhuǎn)變?yōu)楹瘮?shù)對象,然后我們可以用 Boost.Function 來保存它們并稍后調(diào)用。在將圖形用戶界面(GUIs)與如何響應(yīng)用戶的操作進(jìn)行分離時,幾乎總是要使用某種回調(diào)方法。如果這種回調(diào)機(jī)制是基于函數(shù)指針的,就很難避免對可以使用回調(diào)的類型的某些限制,也就增加了界面表現(xiàn)與業(yè)務(wù)邏輯之間的耦合風(fēng)險。通過使用 Boost.Function,我們可以避免這些事情,并且當(dāng)與某個支持參數(shù)綁定的庫結(jié)合使用時,我們可以輕而易舉地把上下文提供給調(diào)用的函數(shù)。這是本庫最常見的用途之一,把業(yè)務(wù)邏輯即從表示層分離出來。

以下例子包含一個藝術(shù)級的磁帶錄音機(jī),定義如下:

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';
}
};

這個磁帶錄音機(jī)可以從一個GUI進(jìn)行控制,或者也可能從一個腳本客戶端進(jìn)行控制,或者從別的源進(jìn)行控制,這意味著我們不想把這些函數(shù)的執(zhí)行與它們的實現(xiàn)耦合起來。建立這種分離的一個常用的方法是,用專門的對象負(fù)責(zé)執(zhí)行命令,而讓客戶對命令如何執(zhí)行毫無所知。這也被稱為命令模式(Command pattern),并且在它非常有用。這種模式的特定實現(xiàn)中的一個問題是,需要為每個命令創(chuàng)建單獨(dú)的類。以下片斷示范了它看起來是個什么樣子:

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();
}
};

這并不是一個非常吸引的方案,因為它使得代碼膨脹,有許多簡單的命令類,而它們只是簡單地負(fù)責(zé)調(diào)用一個對象的單個成員函數(shù)。有時候,這是必需的,因為這些命令可能需要實現(xiàn)業(yè)務(wù)邏輯和調(diào)用函數(shù),但通常它只是由于我們所使用的工具有所限制而已。這些命令類可以這樣使用:

int main() {
tape_recorder tr;

// 使用命令模式
command_base* pPlay=new play_command(&tr);
command_base* pStop=new stop_command(&tr);

// 在按下某個按鈕時調(diào)用
pPlay->execute();
pStop->execute();

delete pPlay;
delete pStop;
}

現(xiàn)在,不用再創(chuàng)建額外的具體的命令類,如果我們實現(xiàn)的命令都是調(diào)用一個返回 void 且沒有參數(shù)(先暫時忽略函數(shù) record, 它帶有一個參數(shù))的成員函數(shù)的話,我們可以來點(diǎn)泛化。不用再創(chuàng)建一組具體的命令,我們可以在類中保存一個指向正確成員函數(shù)的指針。這是邁向正確方向[6]的一大步,就象這樣:

[6] 雖然損失了一點(diǎn)效率。

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_)();
}
};

這個命令模式的實現(xiàn)要好多了,因為它不需要我們再創(chuàng)建一組完成相同事情的獨(dú)立的類。這里的不同在于我們保存了一個 tape_recorder 成員函數(shù)指針在 func_ 中,它要在構(gòu)造函數(shù)中提供。命令的執(zhí)行部分可能并不是你要展現(xiàn)給你的朋友看的東西,因為成員指針操作符對于一些人來說可能還不太熟悉。但是,這可以被看為一個低層的實現(xiàn)細(xì)節(jié),所以還算好。有了這個類,我們可以進(jìn)行泛化處理,不再需要實現(xiàn)單獨(dú)的命令類。

int main() {
tape_recorder tr;

// 使用改進(jìn)的命令模式
command_base* pPlay=
new tape_recorder_command(&tr,&tape_recorder::play);
command_base* pStop=
new tape_recorder_command(&tr,&tape_recorder::stop);

// 從一個GUI或一個腳本客戶端進(jìn)行調(diào)用
pPlay->execute();
pStop->execute();

delete pPlay;
delete pStop;
}

你可能還沒有理解,我們已經(jīng)在開始實現(xiàn)一個簡單的 boost::function 版本,它已經(jīng)可以做到我們想要的。不要重復(fù)發(fā)明輪子,讓我們重點(diǎn)關(guān)注手邊的工作:分離調(diào)用與實現(xiàn)。以下是一個全新實現(xiàn)的 command 類,它更容易編寫、維護(hù)以及理解。

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,我們可以立即從同時兼容函數(shù)和函數(shù)對象——包括由綁定器生成的函數(shù)對象——的靈活性之中獲益。這個 command 類把函數(shù)保存在一個返回 void 且不接受參數(shù)的 boost::function 中。為了讓這個類更加靈活,我們提供了在運(yùn)行期修改函數(shù)對象的方法,使用一個泛型的成員函數(shù),set_function.

template <typename Func> void set_function(Func f) {
f_=f;
}

通過使用泛型方法,任何函數(shù)、函數(shù)對象,或者綁定器都兼容于我們的 command 類。我們也可以選擇把 boost:: function 作為參數(shù),并使用 function 的轉(zhuǎn)型構(gòu)造函數(shù)來達(dá)到同樣的效果。這個 command 類非常通用,我們可以把它用于我們的 tape_recorder 類或者別的地方。與前面的使用一個基類與多個具體派生類(在那里我們使用指針來實現(xiàn)多態(tài)的行為)的方法相比,還有一個額外的優(yōu)點(diǎn)就是,它更容易管理生存期問題,我們不再需要刪除命令對象,它們可以按值傳遞和保存。我們在布爾上下文中使用 function f_ 來測試命令是否可用。如果函數(shù)不包含一個目標(biāo),即一個函數(shù)或函數(shù)對象,它將返回 false, 這意味著我們不能調(diào)用它。這個測試在 execute 的實現(xiàn)中進(jìn)行。以下是使用我們這個新類的一個例子:

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控制中調(diào)用...
if (play.enabled()) {
play.execute();
}

// 從某些腳本客戶端調(diào)用...
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();
}

為了創(chuàng)建一個具體的命令,我們使用 Boost.Bind 來創(chuàng)建函數(shù)對象,當(dāng)通過這些對象的調(diào)用操作符進(jìn)行調(diào)用時,就會調(diào)用正確的 tape_recorder 成員函數(shù)。這些函數(shù)對象是自完備的;它們無參函數(shù)對象,即它們可以直接調(diào)用,無須傳入?yún)?shù),這正是 boost::function<void()> 所表示的。換言之,以下代碼片斷創(chuàng)建了一個函數(shù)對象,它在配置好的 tape_recorder 實例上調(diào)用成員函數(shù) play 。

boost::bind(&tape_recorder::play,&tr)

通常,我們不能保存 bind 所返回的函數(shù)對象,但由于 Boost.Function 兼容于任何函數(shù)對象,所以它可以。

boost::function<void()> f(boost::bind(&tape_recorder::play,&tr));

注意,這個類也支持調(diào)用 record, 它帶有一個類型為 const std::string& 的參數(shù),這是由于成員函數(shù) set_function. 因為這個函數(shù)對象必須是無參的,所以我們需要綁定上下文以便 record 仍舊能夠獲得它的參數(shù)。當(dāng)然,這是綁定器的工作。因而,在調(diào)用 record 之前,我們創(chuàng)建一個包含被錄音的字符串的函數(shù)對象。

std::string s="What a beautiful morning...";
record.set_function(
boost::bind(&tape_recorder::record,&tr,s));

執(zhí)行這個保存在 record 的函數(shù)對象,將在 tape_recorder 實例 tr 上執(zhí)行 tape_recorder::record,并傳入字符串。有了 Boost.Function 和 Boost.Bind, 就可以實現(xiàn)解耦,讓調(diào)用代碼對于被調(diào)用代碼一無所知。以這種方式結(jié)合使用這兩個庫非常有用。你已經(jīng)在這個 command 類中看到了,現(xiàn)在我們該清理一下了。由于 Boost.Function 的杰出功能,你所需的只是以下代碼:

typedef boost::function<void()> command;

與 Boost.Function 一起使用 Boost.Lambda

與 Boost.Function 兼容于由 Boost.Bind 創(chuàng)建的函數(shù)對象一樣,它也支持由 Boost.Lambda 創(chuàng)建的函數(shù)對象。你用 Lambda 庫創(chuàng)建的任何函數(shù)對象都兼容于相應(yīng)的 boost::function. 我們在前一節(jié)已經(jīng)討論了基于綁定的一些內(nèi)容,使用 Boost.Lambda 的主要不同之處是它能做得更多。我們可以輕易地創(chuàng)建一些小的、無名的函數(shù),并把它們保存在 boost::function 實例中以用于后續(xù)的調(diào)用。我們已經(jīng)在前一章中討論了 lambda 表達(dá)式,在那一章的所有例子中所創(chuàng)建的函數(shù)對象都可以保存在一個 function 實例中。function 與創(chuàng)建函數(shù)對象的庫的結(jié)合使用會非常強(qiáng)大。

代價的考慮

有一句諺語說,世界上沒有免費(fèi)的午餐,對于 Boost.Function 來說也是如此。與使用函數(shù)指針相比,使用 Boost.Function 也有一些缺點(diǎn),特別是對象大小的增加。顯然,一個函數(shù)指針只占用一個函數(shù)指針的空間大小(這當(dāng)然了!),而一個 boost::function實例占的空間有三倍大。如果需要大量的回調(diào)函數(shù),這可能會成為一個問題。函數(shù)指針在調(diào)用時的效率也稍高一些,因為函數(shù)指針是被直接調(diào)用的,而 Boost.Function 可能需要使用兩次函數(shù)指針的調(diào)用。最后,可能在某些需要與C庫保持后向兼容的情形下,只能使用函數(shù)指針。

雖然 Boost.Function 可能存在這些缺點(diǎn),但是通常它們都不是什么實際問題。額外增加的大小非常小,而且(可能存在的)額外的函數(shù)指針調(diào)用所帶來的代價與真正執(zhí)行目標(biāo)函數(shù)所花費(fèi)的時間相比通常都是非常小的。要求使用函數(shù)而不能使用 Boost.Function 的情形非常罕見。使用這個庫所帶來的巨大優(yōu)點(diǎn)及靈活性顯然超出這些代價。

幕后的細(xì)節(jié)

至少了解一下這個庫如何工作的基礎(chǔ)知識是非常值得的。我們來看一下保存并調(diào)用一個函數(shù)指針、一個成員函數(shù)指針和一個函數(shù)對象這三種情形。這三種情形是不同的。要真正看到 Boost.Function 如何工作,只有看源代碼——不過我們的做法有些不同,我們試著搞清楚這些不同的版本究竟在處理方法上有些什么不同。我們也有一個不同要求的類,即當(dāng)調(diào)用一個成員函數(shù)時,必須傳遞一個實例的指針給 function1 (這是我們的類的名字)的構(gòu)造函數(shù)。function1 支持只有一個參數(shù)的函數(shù)。與 Boost.Function 相比一個較為寬松的投條件是,即使是對于成員函數(shù),也只需要提供返回類型和參數(shù)類型。這個要求的直接結(jié)果就是,構(gòu)造函數(shù)必須被傳入一個類的實例用于成員函數(shù)的調(diào)用(類型可以自動推斷)。

我們將要采用的方法是,創(chuàng)建一個泛型基類,它聲明了一個虛擬的調(diào)用操作符函數(shù);然后,從這個基類派生三個類,分別支持三種不同形式的函數(shù)調(diào)用。這些類負(fù)責(zé)所有的工作,而另一個類,function1, 依據(jù)其構(gòu)造函數(shù)的參數(shù)來決定實例化哪一個具體類。以下是調(diào)用器的基類,invoker_base.

template <typename R, typename Arg> class invoker_base {
public:
virtual R operator()(Arg arg)=0;
};

接著,我們開始定義 function_ptr_invoker, 它是一個具體調(diào)用器,公有派生自 invoker_base. 它的目的是調(diào)用普通函數(shù)。這個類也接受兩個類型,即返回類型和參數(shù)類型,它們被用于構(gòu)造函數(shù),構(gòu)造函數(shù)接受一個函數(shù)指針作為參數(shù)。

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);
}
};

這個類模板可用于調(diào)用任意一個接受一個參數(shù)的普通函數(shù)。調(diào)用操作符簡單地以給定的參數(shù)調(diào)用保存在 func_ 中的函數(shù)。請注意(的確有些奇怪)聲明一個保存函數(shù)指針的變量的那行代碼。

R (*func_)(Arg);

你也可以用一個 typedef 來讓它好讀一些。

typedef R (*FunctionT)(Arg);
FunctionT func_;

接著,我們需要一個可以處理成員函數(shù)調(diào)用的類模板。記住,它要求在構(gòu)造時給出一個類實例的指針,這一點(diǎn)與 Boost.Function 的做法不一樣。這樣可以節(jié)省我們的打字,因為是編譯器而不是程序員來推導(dǎo)這個類。

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);
}
};

這個類模板與普通函數(shù)指針的那個版本很相似。它與前一個版本的不同在于,構(gòu)造函數(shù)保存了一個成員函數(shù)指針與一個對象指針,而調(diào)用操作符則在該對象(t_)上調(diào)用該成員函數(shù)(func_)。

最后,我們需要一個兼容函數(shù)對象的版本。這是所有實現(xiàn)中最容易的一個,至少在我們的方法中是這樣。通過使用單個模板參數(shù),我們只表明類型 T 必須是一個真正的函數(shù)對象,因為我們想要調(diào)用它。說得夠多了。

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);
}
};

現(xiàn)在我們已經(jīng)有了這些適用的積木,剩下來的就是把它們放在一起組成我們的自己的 boost::function, 即 function1 類。我們想要一種辦法來發(fā)現(xiàn)要實例化哪一個調(diào)用器。然后我們可以把它存入一個 invoker_base 指針。這里的竊門就是,提供一些構(gòu)造函數(shù),它們有能力去檢查對于給出的參數(shù),哪種調(diào)用器是正確的。這僅僅是重載而已,用了一點(diǎn)點(diǎn)手法,包括泛化兩個構(gòu)造函數(shù)。

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_;
}
};

如你所見,這里面最難的部分是正確地定義出推導(dǎo)系統(tǒng)以支持函數(shù)指針、類成員函數(shù)以及函數(shù)對象。無論使用何種設(shè)計來實現(xiàn)這類功能的庫,這都是必須的。最后,給出一些例子來測試我們這個方案。

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 類可以接受以下所有函數(shù)。

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 庫所返回的函數(shù)對象。我們的類與 Boost.Function 中的類相比要簡單多了,但是也已經(jīng)足以看出創(chuàng)建和使用這樣一個庫的問題以及相關(guān)解決方法。知道一點(diǎn)關(guān)于一個庫是如何實現(xiàn)的事情,對于有效使用這個庫是非常有用的。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    亚洲综合伊人五月天中文| 正在播放玩弄漂亮少妇高潮| 91福利视频日本免费看看| 免费一区二区三区少妇| 风韵人妻丰满熟妇老熟女av| 99热九九热这里只有精品| 青草草在线视频免费视频| 国产精品欧美激情在线| 亚洲国产精品久久综合网| 欧美一区二区三区99| 久久精品国产一区久久久| 九九热视频免费在线视频| 91福利视频日本免费看看| 东京不热免费观看日本| 青青草草免费在线视频| 91久久精品中文内射| 亚洲欧美日韩综合在线成成| 精品国产亚洲一区二区三区| 熟妇久久人妻中文字幕| 亚洲欧美天堂精品在线| 91久久精品国产成人| 日韩精品视频香蕉视频| 亚洲专区中文字幕视频| 91亚洲精品国产一区| 国产又粗又硬又大又爽的视频| 日韩欧美中文字幕人妻| 亚洲av秘片一区二区三区| 久久久免费精品人妻一区二区三区| 欧美国产精品区一区二区三区| 99久久精品午夜一区| 国产精品一区二区传媒蜜臀| 国产日韩综合一区在线观看| 久久国产人妻一区二区免费| 欧美日韩久久精品一区二区| 日本高清视频在线观看不卡| 91人妻久久精品一区二区三区 | 蜜桃传媒视频麻豆第一区| 伊人久久青草地综合婷婷| 熟女少妇久久一区二区三区| 中文字幕一区久久综合| 国产精品久久三级精品|