STL——仿函數(函數對象)

一、仿函數(也叫函數對象)概觀

仿函數的作用主要在哪裏?從第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 { ... }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章