from:http://blog.csdn.net/dog_in_yellow/archive/2007/02/15/1510538.aspx
《C++設計新思維》一書里的泛化仿函數從Command模式講起。Command模式主要用來降低系統中命令的調用者和執行者間的依存性。設計模式的書里面一般都采用多態的機制,調用者持有Command對象的基類接口,在此處我們稱為Command接口,Command接口不知道自己將被用于執行什么命令,一般只包含一個觸發命令執行的虛函數,假設名為Excute。各種不同的實際執行命令的Command對象從則Command接口派生,并重寫Excute虛函數。這樣調用者通過Command接口來觸發命令執行時,因為虛函數機制的關系,實際上調用的都是從Command接口派生的Command對象的Excute函數。在這樣的設計中,調用者和各種實際的命令對象互不相見,只持有一個Command接口。在C風格的設計中,一般用回調預先保存的函數指針來實現Command模式。
泛化仿函數可以說是一種回調,但它不但可以保存函數指針,還可以處理實現了operator()的C++對象,C++對象的成員函數。具體實現方法書里面已描述得非常詳細,就不再多啰嗦,在此主要說一下項目中運用泛化仿函數的一些心得。基本如下用法:
Functor<RETURN_TYPE,PARAM_TYPE_LIST> cmd1(...);
Functor有兩個泛型參數,第一個為函數返回值,第二個為函數的參數列表。構造函數可接受仿函數、類成員函數以及一般函數指針。下面示例代碼演示了其用法:
#include "loki/functor.h"
#include <iostream>
#include <string>
using namespace std;
using namespace Loki;

struct TestFunctor

...{
int operator()(string str)

...{
cout << str << endl;
return 0;
}
};

struct TestFunctor2

...{
int output(int i)

...{
cout << i << endl;
return 2;
}
};

void TestFunction(int i,int j)

...{
cout << i << "," << j << endl;
}

Functor<int,NullType> BindCmd1()

...{
TestFunctor f;

Functor<int,LOKI_TYPELIST_1(string) > cmd1( f );
Functor<int,NullType> bcmd1 = BindFirst( cmd1, "another bind cmd1" );

return bcmd1;
}

int _tmain(int argc, _TCHAR* argv[])

...{
//泛化仿函數基本用法
//Functor<RETURN_TYPE, PARAM_TYPELIST> cmd(...);

TestFunctor f;
TestFunctor2 f2;

//調用operator()仿函數----------------------------
Functor<int,LOKI_TYPELIST_1(string) > cmd1( f );
cmd1( "1" );
//end of 調用operator()仿函數----------------------


//調用類成員函數----------------------------------
Functor<int,LOKI_TYPELIST_1(int) > cmd2( &f2, &TestFunctor2::output );
cmd2( 2 );
//end of調用類成員函數------------------------------


//調用一般函數指針---------------------------------
Functor<void,LOKI_TYPELIST_2(int,int) > cmd3( TestFunction );
cmd3( 3,4 );
//end of調用一般函數指針---------------------------

//預先綁定命令的參數的調用1
Functor<int,NullType> bcmd1 = BindFirst( cmd1, "bind cmd1" );
bcmd1();
//end of 預先綁定命令的參數的調用1

//預先綁定命令的參數的調用2
Functor<int,NullType> bcmd1_1 = BindCmd1();
bcmd1_1();
//end of 預先綁定命令的參數的調用2


return 0;
}

用法很簡單,上面的幾個用法都只有兩行,第一行定義泛化仿函數,第二行執行仿函數。實際運用中定義和執行一般都各在不同的地方,如Command模式一樣,即它們在時間和空間上是分離的。
如果您使用了LOKI0.1.5的庫,在“預先綁定命令的參數的調用2”的用法中會出現運行時錯誤,這是我在項目過程中碰到的,經過分析LOKI中Functor實現的代碼,終于找到了原因。項目中實際的運用當然不是這樣,示例代碼只說明了在什么情況下運用才會出錯。如代碼所示,調用綁定了參數的仿函數時,如果已經離開了所綁定參數的作用域則會出錯。而其罪魁禍首在于對綁定的參數作了優化。
《C++設計新思維》P123中講到為避免函數轉發的成本對函數參數作了優化,如果參數為非基本類型(非內置類型,如自定義的struct,class),則將參數類型更改為該參數的引用類型。如示例中的int operator()(string str)被優化后str參數變成string &str。而當一個引用已離開其所引用對象的作用域后,該引用會成為一個dead reference,使用了dead reference,不可避免地結局就是運行時錯誤。或許作者不許我們這么使用綁定參數,或者作者沒想到我們會這么使用綁定,但至少說明了一點,過多地優化未必是件好事。
既然知道了為什么會出錯,那就容易解決問題了。綁定的原理是將參數保存起來,在調用的時候取出預先保存的參數傳遞給要調用的函數。如果保存的類型是值類型,那不管是否離開原參數的作用域都不會出錯。現在我們來找實現參數綁定的類定義。在Functor.h中找到class BinderFirst,該類中有一個 類型定義如下:
typedef typename Private::BinderFirstBoundTypeStorage<
typename Private::BinderFirstTraits<OriginalFunctor>
::OriginalParm1>::RefOrValue
BoundTypeStorage;
BoundTypeStorage即保存所綁定參數的類型定義,觀其定義可以知道該類型也是做了優化,非基本類型都變成了引用。現在我們來做一點小改動,使用參數原來的類型來保存參數,修改后的定義如下:
typedef typename Private::BinderFirstTraits<OriginalFunctor>
::OriginalParm1
BoundTypeStorage;
修改完畢后,重新編譯運行,一切OK了。
其實上述改動并不能解決所有問題,這種做法是使用參數的原始類型來保存參數,假如參數本身是個引用類型,那在離開了所引用對象的作用域時調用還是會出錯的。最徹底的改法是將引用參數去掉引用作為保存參數的類型。我的做法是在使用的時候做文章,不管參數是否引用類型,定義綁定的Functor時都定義成非引用類型,這樣再配合上面的改動,參數必然會使用值類型保存。如果明確地知道調用時不會離開原參數的作用域,那就不必如此了。只要心中有個底,具體用法就視個人運用的環境以及個人做法的喜好了。
LOKI0.1.5下載地址:http://sourceforge.net/forum/forum.php?forum_id=583500