众所周知,function可以封装任意类型的可调用对象,功能十分强大,这篇文章主要是想讲解一下function的工作原理。
可调用对象有这么几种类型:仿函数对象、函数指针、lambda表达式... ... 即使这几种可调用对象的参数和返回值类型相同也会被会被判定为不同的类型,毕竟仿函数本质上是类,而函数指针是一个函数指针类型。我们首先要解决的问题就是如何接收不同的类型,答案是模板:
class function { public: template<class T> function(T t) { _t = t; } }那么问题又来了,function类不知道T这个类型,如何提前定义一个对象(_t)去存储传参进来的可调用对象呢?答案是基类和子类:
class function { public: class Base { public: //虚析构函数保证能够正确调用child1的析构函数,否则调用的是Base的析构函数 virtual ~Base() = default; virtual void revoke() = 0; }; template<class T> class child1 : public Base { T _t; public: child1(T t) : _t(t) {} void revoke() override {_t();} }; template<class T> function(T t) { _base = new child1<T>(t); } ~function() { delete _base; } private: //无论可调用对象是什么类型它永远是Base的子类 Base* _base; };为了包装器可以像函数一样被调用,我们再重载一个():
class function { public: class Base { public: //虚析构函数保证能够正确调用child1的析构函数,否则调用的是Base的析构函数 virtual ~Base() = default; virtual void revoke() = 0; }; template<class T> class child1 : public Base { T _t; public: child1(T t) : _t(t) {} void revoke() override {_t();} }; template<class T> function(T t) { _base = new child1<T>(t); } ~function() { delete _base; } void operator()() { _base->revoke(); } private: Base* _base; };到这儿,整个包装器的原理大致就结束了,不过我们可以发现,现在的包装器只能接受无参并且无返回值的可调用对象。这就是为什么定义包装器的时候要传模版类型:
//不能主模板中偏特化,所以先声明主模板 template<class Ret,class... Args> class function; template<class Ret,class... Args> class function<Ret(Args...)>//这是一种偏特化,定义了传模板类型的格式 { public: class Base { public: //虚析构函数保证能够正确调用child1的析构函数,否则调用的是Base的析构函数 virtual ~Base() = default; virtual Ret revoke(Args...) = 0; }; template<class T> class child1 : public Base { T _t; public: child1(T t) : _t(t) {} Ret revoke(Args... args) override {_return _t(args...);} }; template<class T> function(T t) { _base = new child1<T>(t); } ~function() { delete _base; } Ret operator()(Args... args) { return _base->revoke(args...); } private: Base* _base; };