前面在做 http server 的時候,需要做一個回調的接口,要求能夠綁定類的函數以及普通的函數到這個回調里,對於這種應用要求,選擇 boost 的 bind 和 function 是最合適不過了,但現在情況有些不同,我不准備在現在做的這個東西里加入 boost, 本着以造輪子為樂的精神,現在只能捋起袖子自己來搞一個。
大概原型
使用的時候一直沒有太留意它們的實現,現在要做起來,發現也不是想像中那么輕而易舉。這個東西做到最后要實現的效果就是設計一個泛型的 function holder,這個 holder 既能包裝類的函數,又要能包裝一般的函數,換言之就是能像下面一樣來使用。
#include <iostream>
using namespace std; class cs { public: int proc(double d) { cout << "mem func proc:" << d << endl; return (int)d;} }; int Proc(double d) { cout << "normal proc:" << d << endl; return (int)d; } int main() { function fun = &Proc; fun(2.3); cs c; fun = bind(&cs::proc, &c); fun(3.3); return 0; }
簡單實現
一開始你可能會想,a piece of cake! 直接封裝一個 function 就行了。
template<class ret_type, class arg_type>
class function1: public copyable { public: typedef ret_type (* NORM_PROC) (arg_type); function1(NORM_PROC proc = 0): fun_(proc){} ret_type operator() (arg_type arg) { fun_->operator()(arg); } private: NORM_PROC fun_; };
好,這個類可以封裝一般的函數了,那類的函數呢?one more!
template<class CS, class ret_type, class arg_type>
class function2: public copyable { public:
typedef ret_type (CS::* MEM_PROC)(arg_type); function2(CS* obj, MEM_PROC proc): obj_(obj), proc_(proc) {} ret_type operator() (arg_type arg) { return (obj_->*proc_)(arg); } private: CS* obj_;
MEM_PROC proc_; };
很快我們就發現有問題了,function1 和 function2 是兩不同的模板類,bind() 的時候沒法處理:bind() 返回的應該要是一個統一的類型。怎么辦呢?我們可能想到要抽取出一個基類來,思路是對的!但還有些細節要處理。比如:bind() 返回的是什么類型呢?function1,function2 的基類嗎?這好像做不到,不能直接返回 object,所以下面的做法是錯的。
template<class ret_type, class arg_type>
class function_base: public copyable { public:
virtual ~function_base(){} virtual ret_type operator() (arg_type arg) = 0; }; template<class CS, class ret_type, class arg_type>
class function2: public function_base<ret_type, arg_type> { public: typedef ret_type (CS::* MEM_PROC)(arg_type); function2(CS* obj, MEM_PROC proc): obj_(obj), proc_(proc) {} ret_type operator() (arg_type arg) { return (obj_->*proc_)(arg); } private: CS* obj_;
MEM_PROC proc_; }; template<class CS, class ret_type, class arg_type> function_base<ret_type, arg_type> bind(ret_type (CS::* proc)(arg_type), CS* pc) { function2<CS, ret_type, arg_type> func_holder(pc, proc); return func_holder; // object slicing }
那直接返回指針不就完了!返回指針可行,但不好用,而且容易內存泄漏。解決的辦法是對返回的指針再包一層,嗯,RAII。但等等,好像如果再包一層,就已經能直接隔開底下的 function holder 與具體的調用了啊!Perfect!
template<class ret_type, class arg_type>
class function_base: public copyable { public:
virtual ~function_base() {} virtual ret_type operator() (arg_type arg) = 0; }; template<class ret_type, class arg_type>
class function1: public function_base<ret_type, arg_type> { public:
typedef ret_type (* NORM_PROC) (arg_type);
function1(NORM_PROC proc = 0): fun_(proc){}
ret_type operator() (arg_type arg) { fun_->operator()(arg); }
private:
NORM_PROC fun_;
}; template<class CS, class ret_type, class arg_type>
class function2: public function_base<ret_type, arg_type> { public: typedef ret_type (CS::* MEM_PROC)(arg_type); function2(CS* obj, MEM_PROC proc): obj_(obj), proc_(proc) {} ret_type operator() (arg_type arg) { return (obj_->*proc_)(arg); } private: CS* obj_;
MEM_PROC proc_; };
template<class ret_type, class arg_type>
class functioin: public copyable
{
public:
function(function_base<ret_type, arg_type>* pf): _obj(pf) {}
ret_type operator()(arg_type arg){obj_->operator()(arg);}
private:
function_base<ret_type, arg_type>* obj_;
};
template<class CS, class ret_type, class arg_type> function<ret_type, arg_type> bind(ret_type (CS::* proc)(arg_type), CS* pc) { return new function2<CS, ret_type, arg_type>(pc, proc); }
經過這樣一包裝,function 類好像已經能夠用來 bind 類的成員函數了, 也沒那么難嘛!但是,代碼很差勁:
1) 沒有處理內存釋放。
2) 沒有處理 copy costructor,assignment operator()。
3) 普通函數還是不能直接賦值給 function 類。
再改一下 function 類:
template<class ret_type, class arg_type>
class function { public: typedef ret_type (* NORM_PROC) (arg_type); function(function_base<ret_type, arg_type>* fun): fun_(fun), ref_(new int(1)) {} function(NORM_PROC proc = 0): fun_(new function1<ret_type, arg_type>(proc)), ref_(new int(1)) {} ret_type operator() (arg_type arg) { fun_->operator()(arg); } ~function() { Release(); } void Release() { *ref_ -= 1; if (*ref_ == 0) { delete ref_; delete fun_; } } function(const function& fun) { fun_ = fun.fun_; ref_ = fun.ref_; *ref_ += 1; } void operator=(const function& fun) { Release(); fun_ = fun.fun_; ref_ = fun.ref_; *ref_ += 1; } private: int* ref_; function_base<ret_type, arg_type>* fun_; };
這樣一來,終於能夠正常使用了,可以看到,為了使得 function 類能被 copy/assign,這里面使用引用計數來控制內存的釋放問題,上面的實現比較簡單,也不是線程安全的,只是滿足了基本的使用需求,具體的代碼參看這里。代碼寫得較快,暫且就這樣了,不知道 boost 是怎樣實現的?得找個時間研究研究。