从放弃到重启 C++—仿函数(functor)

880 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

  • 什么是仿函数
  • 仿函数的作用

仿函数

仿函数又称为函数对象,可能你对函数和对象这两个概念都比较熟悉,但是函数对象在一起可能会感觉有些陌生,下面通过一个例子给大家解释一下什么是函数对象,以及函数对象和函数区别,以及我们为什么要引入函数对象。

仿函数应用

偏函数实现

struct Object
{
    int operator()(int i) const{
        return i;
    }
};
int func(int i)
{
    return i;
}
int main()
{
    Object obj;
    int a = 2;
    int res = obj(a);
    
    int res_func = func(a);
    
    cout << "result of functor: " << res << endl;
    cout << "result of function: " << res << endl;
    
    cout<<"Hello World";

    return 0;
}

这里我们来看函数对象和对象函数使用并没有什么区别,接下来我们来看一个一个有趣的例子

struct AddFunctor
{
    int operator()(int i,int j) const{
        return i;
    }
};

int Add(int i,int j)
{
    return i;
}

int main()
{
    AddFunctor addFunctor;
    
    int a = 1;
    int b = 2;
    
    addFunctor(a,3);
    addFunctor(b,3);
    
    Add(a,3);
    Add(b,3);
    
    
    
    cout<<"Hello World";

    return 0;
}

上面例子我们不难看出进行两次加法运算,都使用一个共同数值 3,那么我们来看一看如何通过函数对象对其进行优化。因为函数对象本质是一个对象所以其内部可以持有状态,现在让我们对上 AddFunctor 做适当调整,我们可以把其中一个参数作为状态来维护

struct AddFunctor
{
    
    AddFunctor(int t):m_t(t){};
    int operator()(int i) const{
        return i + m_t;
    }
    
    int m_t
};

AddFunctor 创建一个变量 m_t 持有状态, 也就是将加法分解为一个因子固定加法,这些在 JavaScript 中使用通过 curry 化来实现偏函数。其实这就是我们熟悉偏函数,也是函数式编程中常见一种函数式编程思想的体现。

    AddFunctor addFunctor(3);
    
    int a = 1;
    int b = 2;
    
    addFunctor(a);
    addFunctor(b);
    
    Add(a,3);
    Add(b,3);

这里例子看做不算明显,我们举一个相对复杂的例子来进一步说明

void doSomething(int i, bool b, float f,double d,unsigned int u, char c){
    
}
doSomething(1,true,2.f,3.0,5u,'a');
doSomething(1,true,2.f,3.0,5u,'b');

观察一下两次调用doSomething 函数 doSomething(1,true,2.f,3.0,5u,'a'); 就不难发现其中大部分参数都是重复的,我们可以将重复作为函数对象的状态所持有,这样简化工作量,代码也易于维护。


struct DoSomethingFunctor
{
    DoSomethingFunctor(int i,bool b, float f, double d,unsigned int u):
        m_i(i),m_b(b),m_f(f),m_d(d),m_u(u){}
    
    void operator()(char c)const {
        
    }
    
    int m_i;
    bool m_b;
    float m_f;
    double m_d;
    unsigned int m_u;
    
};
    DoSomethingFunctor o(1,true,2.f,3.0,5u);
    
    o('a');
    o('b');

排序

接下来通过一个自定义排序方式来看一看仿函数的另一个应用

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

using namespace std;

struct Tut
{
    Tut(int idx,string title):m_idx(idx),m_title(title){}
    unsigned int m_idx;
    string m_title;
    
    friend ostream& operator<<(ostream& os,const Tut& tut)
    {
        os <<  tut.m_title ;
        return os;
    }
};

int main()
{
    vector<Tut> tuts{Tut(1,"machine learning"),Tut(2,"probability grahical model"),Tut(5,"deep learning"),Tut(3,"segmenation")};
    
    cout << Tut(1,"machine leanring") << endl;
    
    for(auto it = tuts.begin(); it != tuts.end(); it++){
        cout << *it << endl;
        // cout << " " << endl;
    }
    
    cout<<"Hello World";

    return 0;
}
#include<iostream>
#include<string>
#include<vector>
#include <bits/stdc++.h>
class Message
{
public:
	std::string getHeader(const std::string& header_name) const;
};
sort(tuts.begin(),tuts.end());

我们尝试对 tuts 的元素进行排序,因为 Tut 并没有覆写 < 方法所以需要我们提供覆写这个 operator<

error: no match for ‘operator<’ (operand types are ‘Tut’ and ‘Tut’)
bool operator< (Tut const& other) const{
    return m_idx < other.m_idx;
}

我们根据错误提示覆写 operator< 方法, 如果我们想要课程引入价格,然后按价格进行排序。

struct Tut
{
    Tut(int idx,string title, float price):m_idx(idx),m_title(title),m_price(price){}
    unsigned int m_idx;
    float m_price;
    string m_title;
    
    bool operator< (Tut const& other) const{
        return m_idx < other.m_idx;
    }
    
    friend ostream& operator<<(ostream& os,const Tut& tut)
    {
        os <<  tut.m_title ;
        return os;
    }
};

我们可以去修改bool operator< (Tut const& other) const函数内部逻辑,这样可能会影响其他排序位置的逻辑

struct SortByPriceFunctor
{
    bool operator()(Tut const& lhs, Tut const& rhs)const{
        return lhs.m_price < rhs.m_price;
    }
};

int main()
{
    vector<Tut> tuts{
        Tut(1,"machine learning",10.f),
        Tut(2,"probability grahical model",15.f),
        Tut(5,"deep learning",20.f),
        Tut(3,"segmenation",5.f)};
    
    sort(tuts.begin(),tuts.end(),SortByPriceFunctor());
    
    // cout << Tut(1,"machine leanring") << endl;
    
    for(auto it = tuts.begin(); it != tuts.end(); it++){
        cout << *it << endl;
        // cout << " " << endl;
    }
    
    // cout<<"Hello World";

    return 0;
}
class MessageSorter
{
public:
	MessageSorter(const std::string& field) : _field(field) {}
	bool operator()(const Message& lhs, const Message& rhs)
	{
		return lhs.getHeader(_field) < rhs.getHeader(_field);
	}

private:
	std::string _field;
};
int main()
{
	std::vector<Message> messages;
	MessageSorter comparator("some string");
	sort(messages.begin(), messages.end(), comparator);

	std::cout << "hello Functor" << std::endl;
}

我们可以通过仿函数来实现在列表中搜索,

仿函数和函数指针

如果接受函数指针的函数,不能像传递函数指针一样传递仿函数,即使仿函数的参数和返回值与函数指针的参数相同。通常需要传入仿函数,则不能传入一个函数指针

Functors vs. Virtual Functions

其实仿函数虚函数是密切相关的,都可以解决了同一类问题,例如我们创建函数提供了。这里设计一个叫 doMath 的函数,这个函数需要 3 个参数:两个整型的数值以及要这两个数值进行操作的运算操作,这个需求可以仿函数来实现。

typedef int FuncType(int x, int y);

这里定义类型 FuncType 一个函数类型 int FuncType(int x, int y)

class MathComputer
{
public:
    virtual int computeResult(int x, int y) = 0;
};

int doMath(int x, int y, MathComputer* p_computer)
{
    return p_computer->computeResult(x, y);
}

class Add :MathComputer {
public:
    int computeResult(int x, int y) {
            return x + y;
    }
};

首先定义类似抽象类定义 MathComputer 类中定义虚函数 virtual int computeResult(int x, int y) 通过继承于 MathComputer 类并实现需函数来实现其中虚函数来实现多态。

int main()
{

    MathComputer* mathComputer ;
    Add add;
    mathComputer = (MathComputer*)& add;
    int res = doMath(2,3, mathComputer);

    std::cout << res << std::endl;

    return 0;
}