我经常听到很多关于c++中的函子。谁能给我一个概述,他们是什么,在什么情况下他们会有用?


当前回答

函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为std::vector定义一个函子:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

这个函数接受一个std::vector<T>,并在给定一个接受T并返回U的函数F时返回std::vector<U>。一个函子不一定要在容器类型上定义,它也可以为任何模板类型定义,包括std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

下面是一个将类型转换为double类型的简单示例:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

函子应该遵循两条定律。第一个是恒等定律,它指出,如果函子给定了恒等函数,它应该与将恒等函数应用于类型相同,即fmap(identity, x)应该与identity(x)相同:

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

下一个定律是组合定律,它指出,如果函子被赋予两个函数的组合,它应该与将函子应用于第一个函数,然后再应用于第二个函数相同。因此,fmap(std::bind(f, std::bind(g, _1)), x)应该与fmap(f, fmap(g, x))相同:

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

其他回答

就像其他人提到的,函子是一个像函数一样工作的对象,即它重载函数调用操作符。

函子通常用于STL算法。它们很有用,因为它们可以在函数调用之前和之间保持状态,就像函数语言中的闭包一样。例如,你可以定义一个MultiplyBy函子,将它的参数乘以一个指定的量:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

然后你可以传递一个MultiplyBy对象给一个像std::transform:这样的算法:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

函子相对于指向函数的指针的另一个优点是可以在更多情况下内联调用。如果你将一个函数指针传递给transform,除非该调用被内联,并且编译器知道你总是将同一个函数传递给它,否则它不能通过指针内联调用。

在gtkmm中使用函子将一些GUI按钮连接到实际的c++函数或方法。


如果你使用pthread库使你的应用程序多线程,Functors可以帮助你。 要启动一个线程,pthread_create(..)的参数之一是要在自己的线程上执行的函数指针。 但有一个不便之处。这个指针不能是指向方法的指针,除非它是一个静态方法,或者除非你指定了它的类,比如class::method。还有一件事,你方法的接口只能是:

void* method(void* something)

因此,如果不做一些额外的事情,你就不能(以一种简单明显的方式)在线程中运行你的类中的方法。

在c++中处理线程的一个很好的方法是创建你自己的Thread类。如果你想运行MyClass类的方法,我所做的是,把那些方法转换成Functor派生类。

同样,Thread类有这样的方法: 静态void* startThread(void*参数) 指向该方法的指针将用作调用pthread_create(..)的参数。startThread(..)在arg中应该接收到的是一个void*类型的引用,它指向任何Functor派生类的堆中的实例,在执行时将被强制转换回Functor*,然后调用它的run()方法。

函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为std::vector定义一个函子:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

这个函数接受一个std::vector<T>,并在给定一个接受T并返回U的函数F时返回std::vector<U>。一个函子不一定要在容器类型上定义,它也可以为任何模板类型定义,包括std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

下面是一个将类型转换为double类型的简单示例:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

函子应该遵循两条定律。第一个是恒等定律,它指出,如果函子给定了恒等函数,它应该与将恒等函数应用于类型相同,即fmap(identity, x)应该与identity(x)相同:

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

下一个定律是组合定律,它指出,如果函子被赋予两个函数的组合,它应该与将函子应用于第一个函数,然后再应用于第二个函数相同。因此,fmap(std::bind(f, std::bind(g, _1)), x)应该与fmap(f, fmap(g, x))相同:

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

函子也可以用来模拟在函数中定义局部函数。参考这个问题和另一个问题。

但是局部函子不能访问外部的自动变量。lambda (c++ 11)函数是一个更好的解决方案。

小之外。你可以使用boost::function从函数和方法中创建函子,如下所示:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

你可以使用boost::bind为这个函子添加状态

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

最有用的是,使用boost::bind和boost::function,你可以从类方法创建函子函数,实际上这是一个委托:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

你可以创建函子的列表或向量

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

所有这些东西都有一个问题,编译器错误消息不是人类可读的:)