C++的仿函数

190 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

什么是仿函数

仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
在C++里,通过在一个类中重载括号运算符的方法使用一个函数对象而不是一个普通函数

class T {
public:
  void operator()() { cout << "hello world" << endl; }
};
T t;
t();

仿函数有什么用呢?

在很多时候,仿函数是有很多用途的,比如回调什么的
考虑这样一个场景,你需要实现一个count函数,需要根据条件进行统计
有哪几种写法呢?

bool check(int x) { return x == 1; }
int main() { 

  std::vector<int> ve{1, 2, 3, 4, 5};
  struct checks {
    bool operator()(int x) { return x == 1; }
  };
  cout << std::count_if(begin(ve), end(ve), check);
  cout << std::count_if(begin(ve), end(ve), [](int x) { return x == 1; });
  cout << std::count_if(begin(ve), end(ve), checks());
  return 0;
}

同一个接口,stl可以提供多种方式来共使用
在除了函数方式的接口之外,我们注意到,有一个类的对象作为参数传入的进去
这种方式就是仿函数的写法
仿函数也叫做函数对象,你可以把他看作为一种带有状态的函数

有状态的函数

想到了什么?
是不是闭包

没错,这个就可以相当于是一个闭包一样的东西

auto range = []() { return [x = 0]() mutable { return x++; }; };
auto a = range();
auto b = range();
cout << a() << endl;
cout << a() << endl;
cout << a() << endl;
cout << b() << endl;

上述的range会返回一个函数,这个函数有一个x的状态,会随着函数的执行而改变
仿函数和普通函数的区别和优势在什么地方呢?
答案就是这里
仿函数是有状态和变量的,这个可以给我们更大的操作空间

lambda 语法糖

那么,lambda是怎么实现的呢,要知道,在c++中,黑魔法虽然多,但是都是有依据的
那么,c++11是如何支持匿名函数的呢?
要知道,函数内可以不支持定义函数的,在C/C++中
答案是仿函数,因为虽然函数中不可以声明函数,可以可以声明类
在类里面就可以实现函数,间接的实现了一层
且有没有破坏兼容C语言的要求
我们看一个C++ lambda的生成的例子

int main(){
  auto t = [](int a) -> int {
  	return 1;
  };
} 

int main()
{
    
  class __lambda_4_12
  {
    public: 
    inline int operator()(int a) const
    {
      return 1;
    }
    
    using retType_4_12 = auto (*)(int) -> int;
    inline operator retType_4_12 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline int __invoke(int a)
    {
      return __lambda_4_12{}.operator()(a);
    }
    
    public: 
    // inline /*constexpr */ __lambda_4_12(__lambda_4_12 &&) noexcept = default;
    
  };
  
  __lambda_4_12 t = __lambda_4_12(__lambda_4_12{});
  return 0;
}
 

可以看见,lambda就是一个语法糖嘛,我也可以假装实现一下 就是实现的不是这样漂亮

定义部分

#define STRCAT(a, b) a##b
#define LAMBDA_NAME_BODY(a, b) STRCAT(a, b)
#define LAMBDA_NAME LAMBDA_NAME_BODY(__lambda__function__, __COUNTER__)

#define lambda(ret, function_name, body, ...)                                  \
  struct LAMBDA_NAME {                                                         \
    ret operator()(__VA_ARGS__) body                                           \
  } function_name

使用

int main() {
  lambda(
      void, fun, {
        cout << a << " " << b << " " << c << endl;
      },
      int a, int b, int c);
  fun(1, 2, 3);
}