函数对象

174 阅读6分钟

函数对象

函数对象是行为类似函数的对象,是一个定义了operator()的对象,是泛型编程强大威力和纯粹抽象概念的又一个例证。

一个函数对象

任何行东西,其行为像函数,它就是个函数。所谓函数行为是指可以“使用小括号传递实参,借以调用某个东西”,即通过小括号的运用和实参的传递实现调用。要实现这种可能,只需要定义operator(),并给予合适的参数类型,如下所示:

class X{
public:
        // define "function call" operator:
        return value operator()(arguments) const;
        ...   
}

随即便可以把X的对象当作函数来调用了,完整的示例如下所示:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class PrintInt
{
public:
    void operator()(int elem) const
    {
        cout << elem << "  ";
    }
};

int main()
{
    vector<int> coll;
    for (int i = 0; i<=9; ++i)
    {
        coll.push_back(i);
    }
    for_each(coll.cbegin(), coll.cend(), PrintInt());
    cout << endl;
    system("pause");
    return 0;
}

函数对象相对于寻常函数有以下优点:

  1. 函数对象是一种带状态的函数。

    函数对象可拥有成员函数和成员变量,这意味着函数对象拥有状态。事实上,在同一时间点,相同类型的两个不同函数对象所表述的相同机能,可能具备不同的状态。这在寻常函数是不可能的。另一个好处是,可以在运行期初始化它们。

  2. 每个函数对象都有其自己的类型。

    寻常函数,唯有在其签名式不同时才算类型不同。而函数签名即使签名式相同,也可以有不同的类型。由函数对象定义的每一个函数行为都有其自己的类型。这可以将函数行为当作template参数来运用。另外还有个好处:容器类型也会因为函数对象不同而不同。

  3. 函数对象通常比寻常函数速度快。

函数对象拥有内部状态

函数对象表现得像个函数又同时拥有状态,如下例所示。

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

class IntSequence {
private:
    int value;
public:
    IntSequence(int initialValue)
        :value(initialValue)
    {}

    int operator()() {
        return ++value;
    }
};

int main()
{
    list<int> coll;
    generate_n(back_inserter(coll), 9, IntSequence(1));

    for (int i : coll)
    {
        cout << i << "\t";
    }
    cout << endl;

    generate(next(coll.begin()), prev(coll.end()), IntSequence(42));

    for (int i : coll)
    {
        cout << i << "\t";
    }
    cout << endl;

    return 0;
}

在默认情况下函数对象是以值传递的方式传递给算法的,这样算法运行后就不会改变函数对象的状态,这样的好处是可以传递常量表达式或暂态表达式;而缺点则是函数对象的状态无法被算法改变。

IntSequence seq(1);

//insert sequence beginning with 1
generate_n(back_inserter(coll), 9, seq);

//insert sequence beginning with 1 again
//seq do not change
generate_n(back_inserter(coll), 9, seq);

有三个办法可以从使用函数对象的算法中得到结果反馈:

1.在外部持有状态,并让函数对象指向它;

2.以引用的方式传递函数对象;

3.利用for_each()算法的返回值。

为了以引用方式传递函数对象,在调用算法时必须明确标识该函数对象是一个引用,即将算法中template实参明白标示就可以了,如下所示:

IntSequence seq(1);

generate_n<back_insert_iterator<list<int>>, int, IntSequence&>(back_inserter(coll), 9, seq); 

此外,在使用for_each()算法时,不需要以引用的方式传递函数对象来获取返回值,因为for_each()算法可以将其使用的函数对象传回来,可以通过其返回值来获取函数对象的状态。

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

class MeanValue {
private:
    long num;
    long sum;
public:
    MeanValue() :num(0), sum(0) {}

    void operator ()(int elem)
    {
        ++num;
        sum += elem;
    }

    double value()
    {
        return static_cast<double>(sum) / static_cast<double>(num);
    }
};

int main()
{
    vector<int> col = {1, 2, 3, 4, 5, 6, 7, 8 };
    MeanValue mv = for_each(col.begin(), col.end(), MeanValue());
    cout << "mean value:" << mv.value() << endl;

    system("pause");

    return 0;
}

C++中预定义的函数对象

C++标准库内含若干预定义的函数对象,涵盖了许多基础运算。一个典型的例子是作为排序准则的函数对象。Operator <的默认排序准则乃是调用less<>。使用预定义的函数对象的示例如下,其中negate函数对象的作用是将传入的int设为负,multiplies函数对象为乘法运算。

#include <iostream>
#include <deque>
#include <functional>
#include <algorithm>
using namespace std;


int main()
{
    deque<int> coll = {1, 2, 3, 5, 7, 11, 3, 17, 19};
    cout << "initialized:" << endl;
    for (auto elem : coll)
    {
        cout << elem << "  ";
    }
    cout << endl;
    transform(coll.cbegin(), coll.cend(), coll.begin(), negate<int>());
    cout << "negated:" << endl;
    for (auto elem : coll)
    {
        cout << elem << "  ";
    }
    cout << endl;
    transform(coll.cbegin(), coll.cend(),coll.cbegin(), coll.begin(), multiplies<int>());
    cout << "multiplies:" << endl;
    for (auto elem : coll)
    {
        cout << elem << "  ";
    }
    cout << endl;
    system("pause");
    return 0;
}

函数对象的Binder

可以使用特殊的functional adapter(函数适配器)或所谓的binder,将预定义的函数对象和其他数值结合为一体,示例如下:

#include <set>
#include <deque>
#include <functional>
#include <algorithm>
#include <iterator>
#include <iostream>

using namespace std;
using namespace std::placeholders;

int main()
{
    set<int, greater<int>> coll1 = {1, 2, 3, 4, 5, 6 ,7, 8 ,9};
    deque<int> coll2;
    cout << "coll1 initialized:" << endl;
    for (auto elem : coll1)
    {
        cout << elem << "   ";
    }
    cout << endl;
    transform(coll1.cbegin(), coll1.cend(), back_inserter(coll2), bind(multiplies<int>(), _1, 10));
    cout << "coll2 transformed:" << endl;
    for (auto elem : coll2)
    {
        cout << elem << "   ";
    }
    cout << endl;
    replace_if(coll2.begin(), coll2.end(), bind(equal_to<int>(), _1, 70), 42);
    cout << "coll2 replaced:" << endl;
    for (auto elem : coll2)
    {
        cout << elem << "   ";
    }
    cout << endl;
    coll2.erase(remove_if(coll2.begin(), coll2.end(), bind(logical_and<bool>(), bind(greater<int>(), _1, 50), bind(less<int>(), _1, 80))), coll2.end());
    cout << "coll2 removed:" << endl;
    for (auto elem : coll2)
    {
        cout << elem << "   ";
    }
    cout << endl;
    system("pause");
    return 0;
}

bind()允许借由低层的函数对象和占位符合成高层的函数对象。占位符即是“带有前缀下划线”的数值标识符。占位符有自己的命名空间:std::placeholders。通过指定

bind(multiplies<int>(), _1, 10);

便定义出一个函数对象,会将传入的第一个实参乘以10。也可以使用这样一个函数对象,将任何数值乘以10。

auto f = bind(multiplies<int>(), _1, 10);
cout<<f(99)<<endl;

另外还有一种定义函数对象的方法。例如,想要“针对集合内的每个元素”调用某成员函数,可以这么做:

for_each(coll.cbegin(), coll.cend(), bind(&Person::save, _1));

函数对象bind会绑定一个所给的成员函数,后者会因每个元素而被调用。