一、仿函數(也叫函數對象)概觀
仿函數的作用主要在哪里?從第6章可以看出,STL所提供的各種算法,往往有兩個版本,其中一個版本表現出最常用(或最直觀)的某種運算,第二個版本則表現出最泛化的演算流程,允許用戶“以template參數來指定所要采行的策略”。以sort()為例,其第一版本是以operator<為排序時的元素位置調整依據,第二版本則允許用戶指定任何“操作”,務求排序后的兩兩相鄰元素都能令該操作結果為true。
要將某種“操作”當做算法的參數,唯一辦法就是先將該“操作”(可能擁有數條以上的指令)設計為一個函數,再將函數指針當做算法的一個參數;或是將該“操作”設計為一個所謂的仿函數(就語言層面來說是個class),再以該仿函數產生一個對象,並以此對象作為算法的一個參數。
根據以上陳述,既然函數指針可以達到“將整組操作當做算法的參數”,那又何必有所謂的仿函數呢?原因在於函數指針畢竟不能滿足STL對抽象性的要求,也不能滿足軟件積木的要求——函數指針無法和STL其他組件(如配接器adapter)搭配,產生更靈活的變化。同時,函數指針無法保存信息,而仿函數可以。
就實現觀點而言,仿函數其實上就是一個“行為類似函數”的對象。為了能夠“行為類似函數”,其類別定義中必須自定義(或說改寫,重載)function call運算子(operator())。擁有這樣的運算子后,我們就可以在仿函數的對象后面加上一對小括號,以此調用仿函數所定義的operator()。如下:
#include <functional> #include <iostream> using namespace std; int main() { greater<int> ig; cout << boolalpha << ig(4, 6); // cout << greater<int>()(6, 4);
其中第一種用法比較為大家所熟悉,greater<int>ig的意思是產生一個名為ig的對象,ig(4, 6)則是調用其operator() ,並給予兩個參數4,6。第二種用法中的greater<int>() 意思是產生一個臨時(無名的)對 象,之后的(6, 4)才是指定兩個參數6,5。
上述第二種語法在一般情況下不常見,但是對仿函數而言,卻是主流用法。(STL中的仿函數絕大部分采用這種用法)
STL 仿函數的分類,若以操作數的個數划分,可分為一元和二元仿函數;若以功能划分,可分為算術運算、關系運算、邏輯運算三大類。任何應用程序欲使用STL內建的仿函數,都必須含入<functional>頭文件,SGI則將它們實際定義於<stl_function.h>文件中。
二、可配接(adaptable)的關鍵
STL仿函數應該有能力被函數配接器修飾,彼此像積木一樣地串接。為了擁有配接能力,每一個仿函數必須定義自己的相應型別,就像迭代器如果要融入整個STL大家庭,也必須依照規定定義自己的5個相應型別一樣。這些相應型別是為了讓配接器能夠取出,獲得仿函數的某些信息(仿函數能夠保存信息,函數指針則不能)。相應型別都只是一些typedef,所有必要操作在編譯期就全部完成了,對程序的執行效率沒有任何影響,不帶來任何額外負擔。
仿函數的相應型別主要用來表現函數參數型別和傳回值型別。為了方便起見,<stl_function.h>定義了兩個classes,分別代表一元仿函數和二元仿函數(STL不支持三元仿函數),其中沒有任何data members或member functions,唯有一些型別定義。任何仿函數,只要依個人需求選擇繼承其中一個class,便自動擁有了那些相應型別,也就自動擁有了配接能力。
1. unary_function
unary_function 用來呈現一元函數的參數型別和返回值型別。其定義非常簡單:
// STL 規定,每一個Adaptable Unary Function 都應該繼承此類別 template <class Arg, class Result> struct unary_function { typedef Arg argument_type; typedef Result result_type; };
一旦某個仿函數繼承了uanry_function,其用戶便可以取得該仿函數的參數型別,並以相同手法取得其返回值型別;
// 以下仿函數繼承了uanry_function。 template <class T> struct negate:public uanry_function<T,T> { T operator() (const T& x) const { return -x; }; }; // 以下配接器(adapter)用來表示某個仿函數的邏輯負值 template <class Predicate> class unary_negate ... public:
// 模板中,需要typename來指明后面的定義是一個類型 bool operator() (const typename Predicate::argument_type& x) const { .... } };
2. binary_function
binary_function用來呈現二元函數的第一參數型別、第二參數型別,以及返回值型別。其定義非常簡單:
// STL規定,每一個Adaptable Binary Function 都應該繼承此類別 template <class Arg1, class Arg2, class Result> struct binary_function { typedef Arg1 first_argument_type; typedef Arg2 second_argument_type; typedef Result result_type; };
一旦某個仿函數繼承了binary_function,其用戶便可以這樣取得該仿函數的各種型別:
// 以下仿函數繼承了binary_function template <class T> struct plus : public binary_function<T, T, T> { T operator() (const T& x, const T& y) const { return x+y; }; }; // 以下配接器(adapter)用來將某個二元仿函數轉化為一元仿函數 template <class Operation> class binder1st .... protected: Operation op; typename Operation::first_argument_type value; public: // 注意,這里的返回值和參數,都需要加上typename,告訴編譯器其為一個類型值 typename Operation::result_type operator() (const typename Operation::second_argument_type& x) const { ... } };
3. 算術類仿函數 參見相關源碼;
4. 關系運算類仿函數 參見相關源碼;
5. 邏輯運算類仿函數 參見相關源碼;
6. 證同、選擇、投射 參見相關源碼;