前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++:21---仿函数

C++:21---仿函数

作者头像
用户3479834
发布2021-02-03 12:21:49
4530
发布2021-02-03 12:21:49
举报
文章被收录于专栏:游戏开发司机游戏开发司机

什么是仿函数

所谓的仿函数(functor),是通过重载()运算符模拟函数形为的类。

  因此,这里需要明确两点:

  1 仿函数不是函数,它是个类;

  2 仿函数重载了()运算符,使得它的对你可以像函数那样子调用(代码的形式好像是在调用函数)。

假设有一个vector<string>,你的任务是统计长度小于20的字符串的个数,如果使用count_if函数的话,你的代码可能长成这样:

代码语言:javascript
复制
#define STR_LEN  20
bool LengthIsLess(const string& str) {
    return str.length()<STR_LEN;
}
int res=count_if(vec.begin(), vec.end(), LengthIsLess);
代码语言:javascript
复制

  其中count_if函数的第三个参数是一个函数指针,返回一个bool类型的值。

  如果修改LengthIsLess这个函数原型,将原来的宏定义通过参数传进来呢:

bool LengthIsLess(const string& str, int len)

但是他不能满足count_if函数的参数要求:count_if要求的是仅带有一个参数。怎么样找到以上两个函数的一个折中的解决方案呢?

 有三种解决方案可以考虑:

1、函数的局部变量;

局部变量不能在函数调用中传递,而且caller无法访问。

2、函数的参数;

  这种方法我们已经讨论过了,多个参数不适用于count_if函数。

3、全局变量;

  我们可以将长度阈值设置成一个全局变量,代码可能像这样:

代码语言:javascript
复制
int maxLength;
 bool LengthIsLess(const string& str) {
     return str.length()<maxLength;
 }
 int res=count_if(vec.begiin(), vec.end(), LengthIsLess);

  这段代码看似很不错,实则不符合规范,刚重要的是,它不优雅。原因有以下几点要考虑:

1、容易出错;

  为什么这么说呢,我们必须先初始化maxLength的值,才能继续接下来的工作,如果我们忘了,则可能无法得到正确答案。此外,变量maxLength和函数LengthIsLessThan之间是没有必然联系的,编译器无法确定在调用该函数前是否将变量初始化,给码农平添负担。

2、没有可扩展性;

  如果我们每遇到一个类似的问题就新建一个全局变量,尤其是多人合作写代码时,很容易引起命名空间污染(namespace polution)的问题;当范围域内有多个变量时,我们用到的可能不是我们想要的那个。

3、全局变量的问题;

  每当新建一个全局变量,即使是为了coding的便利,我们也要知道我们应该尽可能的少使用全局变量,因为它的cost很高;而且可能暗示你这里有一些待解决的优化方案。

  说了这么多,还是要回到我们原始的那个问题,有什么解决方案呢?答案当然就是这篇blog的正题部分:仿函数。

  我们的初衷是想设计一个unary function,使其能做binary function的工作,这看起来并不容易,但是仿函数能解决这个问题。

代码语言:javascript
复制
class Test{
    public:
        explicit Test(int lenth) : len(lenth){}

        bool operator() (const QString& str) const{
             return str.length() < len;
        }

    private:
        const int len;
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString str = "hello world";
    int len1 = str.length()-1;
    int len2 = str.length()+1;

    //使用方法1
    qDebug()<<Test(len1)(str)<<endl;
    //使用方法2
    Test t(len2);
    qDebug()<<t(str)<<endl;

    return a.exec();
}
代码语言:javascript
复制

  仿函数其实是上述解决方案中的第四种方案:成员变量。成员函数可以很自然的访问成员变量:

代码语言:javascript
复制
class StringAppend{
     public:
         explicit StringAppend(const string& str) : ss(str){}

         void operator() (const string& str) const{
              cout<<str<<' '<<ss<<endl;
         }

     private:
         const string ss;  
 };

 StringAppend myFunc("is world");
 myFunc("hello");

  我相信这个例子能让你体会到一点点仿函数的作用了;它既能想普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。

  让我们回到count_if的问题中去,是不是觉得问题变得豁然开朗了?

代码语言:javascript
复制
class ShorterThan {
    public:
        explicit ShorterThan(int maxLength) : length(maxLength) {}
        bool operator() (const string& str) const {
            return str.length() < length;
        }
    private:
        const int length;
};
count_if(myVector.begin(), myVector.end(), ShorterThan(length));//直接调用即可

  这里需要注意的是,不要纠结于语法问题:ShorterThan(length)似乎并没有调用operator()函数?其实它调用了,创建了一个临时对象。你也可以自己加一些输出语句看一看。

为什么使用仿函数(functor)
  • 迭代和计算逻辑分离

使用仿函数可以使迭代和计算分离开来。因而你的functor可以应用于不同场合,在STL的算法中就大量使用了functor,下面是STL中for_each中使用functor的示例:

代码语言:javascript
复制
struct sum
{
    sum(int * t):total(t){};
    int * total;
    void operator()(int element)
    {
       *total+=element;
    }
};
int main()
{
    int total = 0;
    sum s(&total);
    int arr[] = {0, 1, 2, 3, 4, 5};
    std::for_each(arr, arr+6, s);
    cout << total << endl; // prints total = 15;
}
  • 参数可设置

可以很容易通过给仿函数(functor)设置参数,来实现原本函数指针才能实现的功能,看下面代码:

代码语言:javascript
复制
class CalculateAverageOfPowers
{
public:
    CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {}
    void operator() (float x) { acc += pow(x, p); n++; }
    float getAverage() const { return acc / n; }
private:
    float acc;
    int   n;
    float p;
};

这个仿函数的功能是求给定值平方或立方运算的平均值。只需要这样来声明一个对象即可:

代码语言:javascript
复制
CalculateAverageOfPowers my_cal(2);
  • 有状态

与普通函数另一个区别是仿函数(functor)是有状态的,所以可以进行诸如下面这种操作:

代码语言:javascript
复制
CalculateAverage avg;
avg = std::for_each(dataA.begin(), dataA.end(), avg);
avg = std::for_each(dataB.begin(), dataB.end(), avg);
avg = std::for_each(dataC.begin(), dataC.end(), avg);

对多个不同的数据集进行取平均。

  • 性能

我们看一下2中写的代码:

代码语言:javascript
复制
std::transform(in.begin(), in.end(), out.begin(), add_x(1));

编译器可以准确知道std::transform需要调用哪个函数(add_x::operator)。这意味着它可以内联这个函数调用。而如果使用函数指针,编译器不能直接确定指针指向的函数,而这必须在程序运行时才能得到并调用。

一个例子就是比较std::sort 和qsort ,STL的版本一般要快5-10倍。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么使用仿函数(functor)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档