from:http://blog.csdn.net/dog_in_yellow/archive/2007/02/15/1510538.aspx
《C++設(shè)計(jì)新思維》一書里的泛化仿函數(shù)從Command模式講起。Command模式主要用來降低系統(tǒng)中命令的調(diào)用者和執(zhí)行者間的依存性。設(shè)計(jì)模式的書里面一般都采用多態(tài)的機(jī)制,調(diào)用者持有Command對象的基類接口,在此處我們稱為Command接口,Command接口不知道自己將被用于執(zhí)行什么命令,一般只包含一個(gè)觸發(fā)命令執(zhí)行的虛函數(shù),假設(shè)名為Excute。各種不同的實(shí)際執(zhí)行命令的Command對象從則Command接口派生,并重寫Excute虛函數(shù)。這樣調(diào)用者通過Command接口來觸發(fā)命令執(zhí)行時(shí),因?yàn)樘摵瘮?shù)機(jī)制的關(guān)系,實(shí)際上調(diào)用的都是從Command接口派生的Command對象的Excute函數(shù)。在這樣的設(shè)計(jì)中,調(diào)用者和各種實(shí)際的命令對象互不相見,只持有一個(gè)Command接口。在C風(fēng)格的設(shè)計(jì)中,一般用回調(diào)預(yù)先保存的函數(shù)指針來實(shí)現(xiàn)Command模式。
泛化仿函數(shù)可以說是一種回調(diào),但它不但可以保存函數(shù)指針,還可以處理實(shí)現(xiàn)了operator()的C++對象,C++對象的成員函數(shù)。具體實(shí)現(xiàn)方法書里面已描述得非常詳細(xì),就不再多啰嗦,在此主要說一下項(xiàng)目中運(yùn)用泛化仿函數(shù)的一些心得?;救缦掠梅ǎ?br>Functor<RETURN_TYPE,PARAM_TYPE_LIST> cmd1(...);
Functor有兩個(gè)泛型參數(shù),第一個(gè)為函數(shù)返回值,第二個(gè)為函數(shù)的參數(shù)列表。構(gòu)造函數(shù)可接受仿函數(shù)、類成員函數(shù)以及一般函數(shù)指針。下面示例代碼演示了其用法:
#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[])

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

TestFunctor f;
TestFunctor2 f2;

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


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


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

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

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


return 0;
}

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