仿函數(Functors)

仿函數(Functors),顧名思義,就是用struct或是class來模仿函數,利用重載()來達成,所以會以下列這個型式呈現:
CBase A;    // 就像宣告函數一樣
A();        // 呼叫函數
寫個簡單的例子:

#include <iostream>

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&);               \
  TypeName& operator=(const TypeName&)

class CBase
{
public:
    CBase() {}
    ~CBase() {}

    void operator() () { std::cout << "CBase!!!!!\n";}

private:
    DISALLOW_COPY_AND_ASSIGN(CBase);
};

int main()
{
    CBase a;
    a();
    return 0;
}

另外從 http://www.newty.de/fpt/functor.html 挖出來的例子,用來說明跟一般函數的不同之處:
#include <iostream>

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&);               \
  TypeName& operator=(const TypeName&)

using namespace std;

class TFunctor
{
public:
    // 我們來宣告兩種呼叫的方式,一個為operator(),另一種就是一般的Funtion Call
    virtual void operator()(const char* string) = 0; // call using operator
    virtual void Call(const char* string) = 0;      // call using function
};

template <class TClass> class TSpecificFunctor : public TFunctor
{
private:
    void (TClass::*fpt)(const char*);   // 成員函數指標
    TClass* pt2Object;                  // 物件指標

public:
    // 建構式引入物件指標以及函數指標,以來使用operator()和Call()
    // 第二個引入參數要為void function(const char*)的型式!
    TSpecificFunctor(TClass* _pt2Object, void(TClass::*_fpt)(const char*))
    {
        pt2Object = _pt2Object;
        fpt = _fpt;
    };

    // override operator "()"
    virtual void operator()(const char* string)
    {
        (*pt2Object.*fpt)(string);
    };

    // override function "Call"
    virtual void Call(const char* string)
    {
        (*pt2Object.*fpt)(string);
    };
};

class TClassA
{
public:
    TClassA() {};
    void Display(const char* text) { cout << text << endl; };

private:
    DISALLOW_COPY_AND_ASSIGN(TClassA);
};

class TClassB
{
public:
    TClassB() {};
    void Display(const char* text) { cout << text << endl; };

private:
    DISALLOW_COPY_AND_ASSIGN(TClassB);
};

int main()
{
    TClassA objA;
    TClassB objB;

    TSpecificFunctor<TClassA> specFuncA(&objA, &TClassA::Display);
    TSpecificFunctor<TClassB> specFuncB(&objB, &TClassB::Display);

    specFuncA.Call("TestA!");   // 用Function來呼叫
    specFuncB("TestB!");        // 用()來呼叫

    // 放入陣列裡
    TFunctor* vTable[] = { &specFuncA, &specFuncB };

    vTable[0]->Call("TClassA::Display called!");        // via function "Call"
    (*vTable[1])("TClassB::Display called!");           // via operator "()"

    return 0;
} 
仿函數有下列幾項優點:
  • 仿函數可以不帶痕跡地傳遞上下文參數。而CallBack技術通常使用一個額外的void*參數傳遞。這也是多數人認為CallBack技術醜陋的原因。
  • 更好的性能。
仿函數技術可以獲得更好的性能,這點直觀來講比較難以理解。你可能說,CallBack函數都寫成inline了,怎麼會性能比仿函數差?我們這裡來分析下。我們假設某個函數func(例如上面的std::sort)調用中傳遞了一個CallBack函數,那麼可以分為兩種情況:

  • func是inline函數,並且比較簡單,func呼叫最後被展開了,那麼其中對CallBack函數的呼叫也成為一普通函數呼叫(而不是通過函數指標的間接呼叫),並且如果這個CallBack函數很簡單,那麼也可能同時被展開。在這種情形下,CallBack函數與仿函數性能相同。
  • func是非inline函數的話,或者比較複雜而無法展開(例如上面的std::sort,我們知道它是快速排序,函數因為存在遞回而無法展開)。此時CallBack函數作為一個函數指標傳入,他的程式碼也是無法被展開。而仿函數則不同。雖然func本身複雜不能展開,但是func函數中對仿函數的呼叫是編譯器編譯期間就可以確定並進行inline展開的。因此在這種情形下,仿函數比之於CallBack函數,有著更好的性能。並且,這種性能優勢有時是一種無可比擬的優勢(對於std::sort就是如此,因為元素比較的次數非常巨大,是否可以進行inline展開導致了一種雪崩效應)。

但是仿函數也並不能完全取代掉CallBack函數就是,有些地方還是需要用CallBack技術,不過能用仿函數就盡量使用吧!

部份文字來自於:維尼的蜂巢http://www.newty.de/fpt/functor.html

0 意見:

張貼留言