C++ 的函数对象 (2) - 拙略的实现一个function

445 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本文承接上文函数对象提到的的知识点

C++中,我经常使用函数对象,且用std::function来绑定,可是其中的细节,他是如何优雅的进行绑定的,我却一知半解,于是我用我的方式去尝试实现了一个看上去可以用的function

函数指针+模板

既然函数对象也是有类型的,且每一个函数指针都有他所在的类型,比如int add(int,int)的类型是int(int,int),我们可以在一个类里面放上一个函数指针,用来帮助我们

template <typename FUNC> struct function {
  using func_type = FUNC *;
  func_type oper;
  function(func_type fun) : oper(fun) {}
  template <typename... ARG>
  auto operator()(ARG... arg) -> decltype(oper(arg...)) {
    return oper(arg...);
  }
};

用到了这么几个特性 auto + decltype 来在编译期决定我们函数的返回类型 可变长模板

以上特性都是基于C++11的,且就算在STL中的实现,很多也是要依赖这些东西的

我们声明了函数类型是 FUNC,然后在编译期我们就可以根据这个来直接得到函数指针

然后根据编译期生成的operator 进行调用

上述例子的缺点:

很显然,我们没有把返回值和传参分开

且这样做,对于仿函数,是不可以进行绑定的

利用模板匹配分离返回值和传参

我们可以利用构造函数的匹配来进行传参和返回值的分离

template <typename Ret, typename... Arg> struct function {
  using func_type = Ret (*)(Arg...);
  func_type func;
  function(Ret (*fun)(Arg...)) : func(fun) {}
  Ret operator()(Arg... arg) { return func(arg...); }
};

利用模板特化分离返回值和传参

我们可以根据模板特化,直接指定两者的类型

template <typename Ret, typename... Arg> struct function<Ret(Arg...)> {
  using func_type = Ret (*)(Arg...);
  func_type func;
  function(func_type fun) : func(fun) {}
  Ret operator()(Arg... arg) { return func(arg...); }
};

以上两种办法虽然解决了传参分离的问题,可是并没有解决仿函数不可绑定的问题

多态,一种巧妙的手段

回到例子中来观察,对于一类函数对象如int(int,int)来说,它可能会对于两种形态的东西 1:函数指针 2:仿函数

可不能只简简单单的把这个看成只有两种,这可有成千上万种,对于模板来说,
因为函数类型虽然都是同一个类型,可是仿函数却不一样,因为我们在使用仿函数的时候,
我们不关心其所在的类,但是在模板特化里面,每一个类都会生成一种类型,可是我们的传参和返回值都是固定的 我们需要一点点其他手段来进行类型擦除!

等等,类型擦除?很多类表现出相同的动作?
这不就是多态嘛!

我们可以定义一个基类,把operator()给声明为虚函数,然后我们只需要去调用一个基类指针的仿函数
就可以完美完成我们的需求,也就是不同类都可以统一形式调用

template <typename Ret, typename... Arg> struct function<Ret(Arg...)> {
  struct __function {
    virtual ~__function() = default;
    virtual Ret operator()(Arg... arg) {}
  };
  template <typename FUNC> struct __function_call : public __function {
    ~__function_call() {}
    virtual Ret operator()(Arg... arg) { return func(arg...); }
    FUNC func;
    __function_call(FUNC fun) : func(fun) {}
  };
}

上述需要特化这个模板的原因是因为,一个仿函数对象,我们已经失去了其函数类型,需要手动指出来

完整代码:

template <typename Ret, typename... Arg> struct function {
  using func_type = Ret (*)(Arg...);
  func_type func;
  function(Ret (*fun)(Arg...)) : func(fun) {}
  Ret operator()(Arg... arg) { return func(arg...); }
};

template <typename Ret, typename... Arg> struct function<Ret(Arg...)> {
private:
  struct __function {
    virtual ~__function() = default;
    virtual Ret operator()(Arg... arg) {}
  };
  template <typename FUNC> struct __function_call : public __function {
    ~__function_call() {}
    virtual Ret operator()(Arg... arg) { return func(arg...); }
    FUNC func;

  public:
    __function_call(FUNC fun) : func(fun) {}
  };
  __function *call;

public:
  template <typename FUNC>
  function(FUNC func) : call(new __function_call<FUNC>(func)) {}
  ~function() { delete call; }
  Ret operator()(Arg... arg) { return (*call)(arg...); }
};

最后

写了大半天了,是时候来看看写的东西可不可以满足我们的要求了

int main() {
  function fun1 = add;
  function<int(int, int)> fun2 = add;
  function<int(int, int)> fun3 = add_ob{};
  function<int(int, int)> fun4 = [](int a, int b) { return a + b; };
  std::cout << fun1(1, 2) << std::endl;
  std::cout << fun2(1, 2) << std::endl;
  std::cout << fun3(1, 2) << std::endl;
  std::cout << fun4(1, 2) << std::endl;
  return 0;
}

我们的function完美的适配了函数类型和对象类型
并且可以和lambda配合使用(你应该知道lambda的本质是一个仿函数吧)