我不断听说 C++ 中的函子。有人能给我概述一下它们是什么以及它们在什么情况下有用吗?

有帮助吗?

解决方案

一个算符是非常简单,只是一个类定义了操作者()。这可以让你创建对象,其“模样”的功能:

// this is a functor
struct add_x {
  add_x(int x) : x(x) {}
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

有几个关于仿函数的好东西。其中之一是,不同于常规的功能,它们可以包含状态。上面的例子创建了一个功能,增加了42你给不管它。但是,值42没有硬编码,它被指定为构造函数的参数时,我们创建了函子实例。我可以创建另一个加法器,它增加了27,只是通过调用不同的值的构造。这使得它们很好地定制

作为最后线示出,则经常传递函子作为其他函数的参数,如std ::变换或其它标准库的算法。你可以做同样的,除了常规的函数指针,正如我上面所说,仿函数可以“定制”,因为它们含有的状态,使他们更灵活的(如果我想用一个函数指针,我不得不写一个函数这正是加1到其参数。该函子是通用的,并添加任何你初始化它),他们也可能更有效。在上面的例子中,编译器知道到底哪个功能std::transform应该调用。它应该叫add_x::operator()。这意味着它可以内联函数调用。这使得它刚好一样有效,如果我已经手动呼吁矢量的每个值的功能。

如果我已经通过一个函数指针代替,编译器无法立即看到哪些功能它指向的,所以,除非它执行一些相当复杂的全局优化,它不得不取消引用指针在运行时,然后进行调用

其他提示

小加成。您可以使用 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 ::绑定添加状态函子

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 ::函数可以创建从类方法算符,实际上这是一个代表:

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));

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

一个函子是其作用像的功能的对象。 基本上,一个类定义operator()

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

在实际优点是,算符可容纳状态。

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

名称“函子”传统上用于 范畴论 早在 C++ 出现之前。这与 C++ 函子的概念无关。最好用名字 函数对象 而不是我们在 C++ 中所说的“函子”。这就是其他编程语言调用类似结构的方式。

使用而不是普通函数:

特征:

  • 函数对象可能有状态
  • 函数对象适合 OOP(它的行为与其他对象一样)。

缺点:

  • 给程序带来更多的复杂性。

用于代替函数指针:

特征:

  • 函数对象通常可以被内联

缺点:

  • 在运行时函数对象不能与其他函数对象类型交换(至少除非它扩展了一些基类,因此会带来一些开销)

使用而不是虚函数:

特征:

  • 函数对象(非虚拟)不需要 vtable 和运行时调度,因此在大多数情况下效率更高

缺点:

  • 在运行时函数对象不能与其他函数对象类型交换(至少除非它扩展了一些基类,因此会带来一些开销)

像其他人提及的,一个仿函数是一个对象,就像一个功能,即,它重载函数调用操作。

函子在STL常用算法。他们是有用的,因为他们之前和之间的函数调用保持状态,就像在函数式语言封闭。例如,可以定义一个MultiplyBy算符由指定量乘以它的参数:

class MultiplyBy {
private:
    int factor;

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

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

然后,你可以通过一个MultiplyBy对象比如std的算法::变换:

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,除非的的通话得到了内联和编译器知道你总是传递相同的功能,它不能内联通过指针调用。

有关我们之间像我这样的新手们:一个小小的研究,我想通了什么jalf发布的代码做了之后

一个函子是可被“称为”像的功能的类或结构对象。这是通过重载() operator成为可能。该() operator(不知道其所谓的)可以采取任何数量的参数。其他运营商只需要两个即+ operator只能取两个值(一个在运营商的每一侧),并返回任何值,你已经超负荷它。您可以适应() operator这是什么使得它的灵活性内的任何数量的参数。

要首先创建一个仿函数创建类。然后创建一个构造函数的类与您选择的类型和名称的参数。其次是在由初始化列表相同的语句(使用单一冒号操作,这是我也是新手),这与先前声明的参数的构造函数构造类成员的对象。然后() operator超载。最后,你声明类的私人物品或结构已创建。

我的代码(我发现jalf的变量名混淆)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

如果任何这是不准确的或完全错误随时纠正我!

一个算符是适用的函数的参数化高阶函数(即模板)类型。这是地图高阶函数的推广。例如,我们可以定义函子像这样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>并返回std::vector<U>给定的函数F采用一个T并返回一个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));

下面就是我被迫使用一个仿函数来解决我的问题的实际情况:

我有一组功能(比方说,他们的20),和它们都是相同的,除了每个调用3个特定斑点不同的特定功能。

这是难以置信的废物,和代码重复。通常情况下,我只想传递一个函数指针,并调用,在3点。 (因此,代码只需要出现一次,而不是20倍。)

但后来我意识到,在每种情况下,特定的功能所需要的完全不同的参数简档!有时2个参数,有时5个参数等。

另一种解决办法是有一个基类,其中所述特定功能是在派生类的重写的方法。但我真的想打造这一切的继承,只是让我可以通过一个函数指针????

解决方案:所以我所做的就是,我做了一个包装类(一个“函子”),这是能够调用任何我需要调用的函数。我将其设置提前(其参数等),然后我通过它在代替一个函数指针。现在所谓的代码可以触发函子,不知道是什么在里面发生的事情。它甚至可以把它多次(我需要它来调用3次。)


这就是它 - 其中一个函子原来是明显和容易的解决方案,其允许我从20只起到减少代码的重复1的实际例子

除了在回调所使用的,C ++函子也可以帮助提供的 Matlab的喜欢访问风格到的矩阵类。有一个例如

函子在gtkmm的用于一些GUI按钮连接到实际的C ++函数或方法。


如果您使用的并行线程库,使您的应用程序的多线程,仿函数可以帮助你。结果 要启动一个线程,的pthread_create(..)的论据之一是要在自己的线程中执行的函数指针。结果  但是有一个不便之处。该指针不能为指针的方法,除非它是一个静态方式,或除非你的指定它的类,像class::method。还有一件事,你的方法的接口只能是:

void* method(void* something)

所以你不能运行(在一个简单明显的方式),从你的类方法在一个线程中没有做额外的事情。

处理在C ++中,创建自己的Thread类线程的一个很好的方法。如果你想从运行MyClass类的方法,我所做的就是,改变这些方法分成Functor派生类。

此外,Thread类有此方法: static void* startThread(void* arg)结果 一个指向这个方法将被用来作为一个参数调用pthread_create(..)。和什么startThread(..)应接受arg是void*铸造参考实例中派生类的任何Functor,当被执行时将被转换回Functor*的堆,然后把它称为是run()方法。

要添加上,我使用功能对象以适合现有的传统方法,将命令模式; (唯一的地方OO范式真OCP的美,我觉得);  这里还添加相关函数适配器模式。

假设你的方法具有签名:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

我们将看到我们如何才能适应这样的命令模式 - 对于这一点,首先,你必须写一个成员函数适配器,以便它可以被称为一个函数对象

请注意 - 这是丑陋的,而且可能是你还可以使用升压绑定佣工等,但如果你不能或不想,这是一种方式。

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

此外,我们需要一种辅助方法mem_fun3用于上述类中调用帮助。

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

现在,为了绑定参数,我们必须写一个粘合剂的功能。所以,这里有云:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

和,一个辅助函数使用binder3类 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

现在,我们必须与Command类使用;使用下面的typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

下面是你如何调用它:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

注意:F3();将调用方法task1-> ThreeParameterTask(21,22,23);

此图案中的下列链路的完整上下文

像已经重复,函子是可被视为函数的类(过载操作者())。

他们是在你需要反复或延迟调用一些数据关联到一个功能的情况下非常有用。

例如,仿函数的链表可以被用来实现一个基本的低开销同步协程系统中,任务调度程序,或可中断的文件解析。 例子:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

当然,这些例子本身不那么有用。他们只显示函子怎么可能是有用的,仿函数本身是非常基本的,不灵活,这使得他们小于有用的,例如,是什么助推提供。

实现用作函子的一个很大的优点是,它们可以保持和呼叫之间重用状态。例如,许多动态编程算法,如瓦格纳 - 费歇尔算法用于计算 Levenshtein距离串之间时,通过在一个大表的结果的填充工作。这是非常低效的每函数被调用时分配该表中,所以实现该功能的一个算符和使表的成员变量可以极大地提高性能。

下面是实施瓦格纳 - 费歇尔算法作为算符的示例。注意表是如何在构造函数中分配,然后在operator()重复使用,以调整是必要的。

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

Functor 还可用于模拟在函数内定义局部函数。请参阅 问题其他.

但局部函子无法访问外部自动变量。lambda (C++11) 函数是更好的解决方案。

我“发现”一个非常有趣的使用函子:我使用他们时,我没有一个好名字的一种方法,作为仿函数是没有名字的方法; - )

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top