函子


在呼叫函式時的 () 是呼叫運算子(call operator),你可以重載呼叫運算子。例如:

#include <iostream> 
using namespace std;

struct IntPlus {
    int operator()(int rhs, int lhs) const {
        return rhs + lhs;
    }
};

int main() { 
    IntPlus plus;
    cout << plus(10, 20) << endl;
    return 0; 
} 

在範例中,p 稱為函式物件(function object),又稱為函子(functor),是 Callable 類型,可以像函式一樣地呼叫,範例中的 plus 可以指定給 function<int(int, int)> 型態的變數。

這邊的 IntPlus 實例,相當於 lambda 運算式,[] (int rhs, int lhs) { return rhs + lhs; },lambda 運算式多半編譯為匿名的函子,如果一個 lamdbda 運算式有捕捉變數呢?例如 [a] (int b) { return a + b; },那麼相當於底下的函子:

#include <iostream> 
using namespace std;

int main() { 
    class __anonymous {
        int a;
    public:
        __anonymous(int a) : a(a) {}
        int operator()(int b) const {
            return a + b;
        }
    };

    int a = 10;
    __anonymous f(a);

    cout << f(20) << endl;

    return 0; 
} 

如果一個 lamdbda 運算式以參考方式捕捉變數呢?例如 [&a] { a = 30; },那麼相當於底下的函子:

#include <iostream> 
using namespace std;

int main() { 
    class __anonymous {
        int &a;
    public:
        __anonymous(int &a) : a(a) {}
        void operator()() const {
            a = 30;
        }
    };

    int a = 10;
    __anonymous f(a);

    f();        
    cout << a << endl; // 30

    return 0; 
} 

既然如此,不如就使用 lamdbda 運算式就好了,還需要函子嗎?一種說法因為編譯器會對其最佳化,函子比較有效率,不過就目的來說,因為函子是個物件,它就可以攜帶更多的資訊,例如:

#include <iostream> 
using namespace std;

class PrintLine {
    string sep;
public:
    PrintLine(string sep) : sep(sep) {}
    void operator()(string text) const {
        cout << text << sep;
    }
};

int main() { 
    PrintLine printLf("\n");
    PrintLine printCrLf("\r\n");

    printLf("print lf");
    printCrLf("print crlf");

    return 0; 
} 

還有一個好處是函子可以模版化,在〈高階函式〉中看過,functional 中包含了對應於運算子的函子(Functor),像是 plusminusmultiplies 等,這些函子都模版化了,其中的範例就看過,建立函式物件時就可以指定型態:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

int main() {
    auto add10 = bind(plus<int>{}, _1, 10);
    auto mul5 = bind(multiplies<int>{}, _1, 5); 

    cout << add10(30) << endl;  // 40
    cout << mul5(20) << endl;   // 100

    return 0;
}