要開始使用 Boost.Function, 就要包含頭文件 "boost/function.hpp", 或者某個帶數字的版本,從 "boost/function/function0.hpp" 到 "boost/function/function10.hpp". 若是你知道你想保存在 function 中的函數的參數數量,這樣作可讓編譯器僅包含須要的頭文件。若是包含 "boost/function.hpp", 那麼就會把其它的頭文件也包含進去。ios
理解被存函數的最佳方法是把它想象爲一個普通的函數對象,該函數對象用於封裝另外一個函數(或函數對象)。這個被存的函數的最大用途是它能夠被屢次調用,而無須在建立 function 時當即使用。在聲明 functions 時,聲明中最重要的部分是函數的簽名。這部分便是告訴 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 內,而且不管什麼時候它的值被改變,這些函數都會在一個循環裏被調用。調用這些函數的一種語法是:app
(*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,但若是他們不知道呢?咱們添加到接口上的任何東西都必須及時向用戶解釋清楚。flex
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]
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],咱們就要這樣寫:
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 來保存它們並稍後調用。在將圖形用戶界面(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]的一大步,就象這樣:
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.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 中的類相比要簡單多了,可是也已經足以看出建立和使用這樣一個庫的問題以及相關解決方法。知道一點關於一個庫是如何實現的事情,對於有效使用這個庫是很是有用的。