首页 文章

什么是C仿函数及其用途?

提问于
浏览
737

我一直听到很多关于C语言的玩家 . 有人可以给我一个关于它们是什么的概述以及在什么情况下它们会有用吗?

15 回答

  • 1

    仿函数几乎只是一个定义operator()的类 . 这使您可以创建“看起来像”一个函数的对象:

    // 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 :: transform或其他标准库算法 . 您可以使用常规函数指针执行相同的操作,除了,如上所述,仿函数可以"customized"因为它们包含状态,使它们更灵活(如果我想使用函数指针,我必须编写一个添加的函数它的参数正好是1 . 函子是通用的,并且添加了你用它初始化的任何东西,并且它们也可能更有效 . 在上面的示例中,编译器确切地知道 std::transform 应该调用哪个函数 . 它应该调用 add_x::operator() . 这意味着它可以内联该函数调用 . 这使得它就像我在向量的每个值上手动调用函数一样高效 .

    如果我已经传递了一个函数指针,编译器就无法立即看到它指向哪个函数,所以除非它执行一些相当复杂的全局优化,否则它必须在运行时取消引用指针,然后进行调用 .

  • 9

    一点点补充 . 您可以使用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你可以从类方法创建functor,实际上这是一个委托:

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

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

  • 3

    Functor是一个像函数一样运作的对象 . 基本上,一个定义 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)
    { ....}
    
  • 41

    名字"functor"已经在category theory中被广泛使用,早在C出现之前 . 这与仿函数的C概念无关 . 最好使用名称 function object 而不是我们在C中称为"functor" . 这就是其他编程语言调用类似结构的方式 .

    用而不是普通函数:

    特征:

    • 函数对象可能具有状态

    • 函数对象适合OOP(它的行为与每个其他对象一样) .

    缺点:

    • 使该计划更加复杂 .

    使用而不是函数指针:

    特征:

    • 函数对象通常可以内联

    缺点:

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

    用来代替虚函数:

    特征:

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

    缺点:

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

    与其他人提到的一样,仿函数是一个像函数一样的对象,即它使函数调用运算符重载 .

    函数通常用于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 ,除非该调用内联并且编译器知道您始终将相同的函数传递给它,它不能通过指针内联调用 .

  • 895

    对于像我这样的新手:在经过一番研究之后,我想出了jalf发布的代码 .

    仿函数是一个类或结构对象,它可以像函数一样"called" . 这可以通过重载 () 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.
    };
    

    如果其中任何一个不准确或只是明白错误随时纠正我!

  • 2

    仿函数是higher-order function,它将函数应用于参数化(即模板化)类型 . 它是map高阶函数的推广 . 例如,我们可以为 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> 并在给定函数 F 时返回 std::vector<U> ,该函数需要 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));
    
  • 116

    这是一个实际情况,我被迫使用Functor来解决我的问题:

    我有一组函数(比如20个),它们都是相同的,除了每个函数在3个特定的位置调用不同的特定函数 .

    这是令人难以置信的浪费和代码重复 . 通常我会传入一个函数指针,只需在3个点中调用它 . (因此代码只需要出现一次,而不是二十次 . )

    但后来我意识到,在每种情况下,特定功能需要一个完全不同的参数配置文件!有时2个参数,有时5个参数等 .

    另一种解决方案是拥有一个基类,其中特定函数是派生类中的重写方法 . 但是我真的想要构建所有这些INHERITANCE,这样我才能传递一个函数指针????

    解决方案:所以我做的是,我创建了一个包装类(“Functor”),它能够调用我需要调用的任何函数 . 我提前设置它(带有参数等),然后我传入它而不是函数指针 . 现在被调用的代码可以触发Functor,而不知道内部发生了什么 . 它甚至可以多次调用它(我需要它调用3次 . )


    就是这样 - 一个实用的例子,Functor被证明是一个明显而简单的解决方案,它允许我将代码重复从20个函数减少到1个 .

  • 83

    除了用于回调,C仿函数还可以帮助为 matrix 类提供 Matlab 喜欢的访问样式 . 有example .

  • 30

    在gtkmm中使用函数将一些GUI按钮连接到实际的C函数或方法 .


    如果您使用pthread库来使您的应用程序成为多线程,Functors可以帮助您 .
    要启动一个线程, pthread_create(..) 的一个参数是要在自己的线程上执行的函数指针 .
    但是's one inconvenience. This pointer can'是指向方法的指针,除非它是 static method ,或者除非你 specify it's class ,如 class::method . 另外,你的方法的接口只能是:

    void* method(void* something)
    

    所以你不能(以一种简单明显的方式)运行你的类中的方法,而不需要做额外的事情 .

    在C中处理线程的一种非常好的方法是创建自己的 Thread 类 . 如果你想从 MyClass 类运行方法,我所做的就是将这些方法转换为 Functor 派生类 .

    此外, Thread 类具有此方法: static void* startThread(void* arg)
    指向此方法的指针将用作调用 pthread_create(..) 的参数 . startThread(..) 在arg中应该接收的是 void* 对任何 Functor 派生类的堆中的实例的引用,它在执行时将被转换回 Functor* ,然后调用它的 run() 方法 .

  • 18

    为了补充,我使用了函数对象来将现有的遗留方法适合命令模式; (只有OO范式的美丽真实的OCP感到的地方);还在这里添加相关的功能适配器模式 .

    假设您的方法具有签名:

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

    我们将看到如何使它适合Command模式 - 为此,首先,您必须编写一个成员函数适配器,以便它可以作为函数对象调用 .

    注意 - 这很难看,也许你可以使用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));
    

    }

    现在,为了绑定参数,我们必须编写一个binder函数 . 所以,在这里:

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

    这个模式的完整背景如下link

  • 2

    就像重复一样,仿函数是可以作为函数处理的类(重载operator()) .

    它们对于需要将某些数据与对函数的重复或延迟调用相关联的情况最有用 .

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

    /* 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;
    }
    

    当然,这些例子本身并没有用 . 它们只显示了仿函数是如何有用的,仿函数本身是非常基础和不灵活的,这使得它们比例如boost所提供的功能更少 .

  • 35

    实现函数作为仿函数的一大优点是它们可以在调用之间维护和重用状态 . 例如,许多动态编程算法,如Wagner-Fischer algorithm,用于计算字符串之间的Levenshtein distance,通过填写一个大的结果表来工作 . 每次调用函数时分配此表都非常低效,因此将函数实现为函数并使表成为成员变量可以极大地提高性能 .

    下面是将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];
        }
    };
    
  • -9

    Functor还可用于模拟在函数内定义局部函数 . 请参阅questionanother .

    但是本地仿函数无法访问外部自动变量 . lambda(C 11)函数是更好的解决方案 .

  • 2

    我已经“发现”了一个非常有趣的仿函数用法:当我没有一个方法的好名字时我使用它们,因为仿函数是一个没有名字的方法;-)

相关问题