C++函数对象是什么?它们有什么用途?

1068

我一直听到在C++中有关functor的许多内容。能否给我一个概述,告诉我它们是什么以及在什么情况下它们会有用?


5
这个主题已经在回答这个问题时进行了讨论:https://dev59.com/_nRC5IYBdhLWcg3wYP2h - Luc Touraille
2
它用于在C++中创建闭包。 - copper.hat
5
看下面的回答,如果有人想知道operator()(...)是什么意思:它是重载_"函数调用"_运算符。这只是对()运算符进行操作符重载。不要将operator()与调用名为operator的函数混淆,而是将其视为常规的运算符重载语法。 - zardosht
非常相关:为什么要重载 operator() - Gabriel Staples
14个回答

2
Functors在gtkmm中用于将某个GUI按钮连接到实际的C++函数或方法。
如果您使用pthread库使应用程序支持多线程,Functors可以帮助您。要启动一个线程,pthread_create(..)的参数之一是要在其自己的线程上执行的函数指针。但是有一个不便之处。除非它是静态方法,否则此指针不能是指向方法的指针,除非您指定它的类,例如class::method。另外,您的方法的接口只能是:
void* method(void* something)

所以,如果不进行额外处理,你无法简单明了地在线程中运行你的类的方法。在C++中处理线程的一个非常好的方法是创建自己的Thread类。如果你想要运行MyClass类的方法,我所做的是将这些方法转换为Functor派生类。此外,Thread类有这个方法:static void* startThread(void* arg),指向该方法的指针将被用作调用pthread_create(..)的参数。而startThread(..)应该接收一个指向任何Functor派生类堆实例的void*强制转换引用,当执行时将其转换回Functor*,然后调用它的run()方法。

有例子吗? - user3091673

2
将函数实现为函数对象的一个重要优点是它们可以在调用之间维护和重复使用状态。例如,许多动态规划算法(如用于计算字符串之间的Levenshtein距离的Wagner-Fischer算法)通过填充大量结果表来工作。每次调用函数时分配这个表非常低效,因此将函数实现为函数对象,并将表作为成员变量,可以极大地提高性能。

以下是将Wagner-Fischer算法实现为函数对象的示例。请注意,在构造函数中分配了表,然后在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];
    }
};

1

Functor可以用来模拟在函数内定义本地函数。请参考问题另一个

但是,本地functor无法访问外部auto变量。Lambda(C++11)函数是更好的解决方案。


1
此外,我使用函数对象将现有的遗留方法适配到命令模式中(这是我感受到面向对象范式真正开放封闭原则的唯一地方);同时在此添加相关的函数适配器模式。
假设您的方法具有以下签名:
int CTask::ThreeParameterTask(int par1, int par2, int par3)

我们将看到如何将其适配为命令模式 - 为此,首先您必须编写一个成员函数适配器,以便它可以被调用为函数对象。
请注意 - 这很丑陋,您可能可以使用Boost绑定助手等工具,但如果您不能或不想使用它们,这是一种方法。
// 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);
该模式的完整上下文请参见以下链接

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接