std::function的简易实现
本节我们来实现一个简易的std::function
我们知道std::function
是用来包装可调用对象
的,在C++
中,可调用对象包括 普通函数、lambda表达式、重载了()操作符的类对象、类静态函数、类成员函数这几类。
C++程序的编译顺序:预处理(xxx.i)
编译(xxx.s)
汇编(xxx.obj)
链接(xxx.exe / linux可执行文件)
;
模板类(函数)的实例化,是在 预处理之后,编译之前;所以模板并不是真正可执行的C++代码,应该将它理解成一个模具
,我们使用什么样的材料(模板参数),就会生成什么样的代码;
模板类(函数)的模板参数,其实只是一个类型占位符;我们可以赋予这个占位符任何意义,模板类(函数)的实现也是基于这个我们赋予的意义
来进行编写的;所以在使用模板类(函数)时,只要传入的模板参数能满足我们赋予的意义
,这个模板类(函数)就能正常运行
模板函数的模板参数是可以使用编译器自动推导,而模板类的模板参数,却需要显示指定,无法使用自动推导
首先回顾一下 std::function
的使用,举个比较简单的例子:
int func(int x, int y) { return x + y; }
std::function<int, (int, int)> f1(func); //使用1
std::function<int, (int, int)> f2([int x, int y](){ return x + y; }); //使用2
class Test
{
int memFunc(int x, int y) { return x + y; }
}
Test t;
std::function<int, (int, int)> f2(&Test::memFunc, &t); //使用3
可以看到,我们给std::function
传递模板参数时,模板参数
的形式为 ReturnType (Params)
观察使用1
使用2
可以发现,构造函数的参数形式为一个可以被执行的函数
(1中为普通函数,2中为lambda表达式)
结合我们以上发现的特点,最容易想到形式是:
模板参数传递 返回值类型
参数包
, 构造函数传递一个可执行的函数
; 然后重载 ()
操作符,将参数包转发到构造函数传递的可调用函数
中去,执行一下该函数即可;
//代码段1
template <typename Ret, typename... Args>
struct myfunction
{
template <typename Functor>
myfunction(Functor&& func)
{
// 将 func 存储到 m_func 中,
// m_func(std::forward<Functor>(func));
}
Ret operator()(Args&&... args)
{
//调用 m_func 即可
// return m_func(std::forward<Args>(args)...);
}
};
这里其实存在一个问题,那就是我们其实并没有这个m_func
成员变量;这个成员变量的类型应该是和构造函数的模板参数一个类型(Functor类型
),但是这个Functor
只对构造函数可见,所以我们没办法定义一个 Functor m_func;
的成员变量;
那怎么解决?
自然而然就会想到,将 Functor
模板参数从构造函数的模板参数,移动到类模板参数中去,扩大了作用域;自然构造函数就不再是模板函数
//代码段2
template <typename Functor, typename Ret, typename... Args>
struct myfunction
{
myfunction(Functor&& func)
{
m_func(std::forward<Functor>(func));
}
Ret operator()(Args&&... args)
{
return m_func(std::forward<Args>(args)...);
}
Functor m_func;
};
这样做固然可以,但是使用时我们是不是得传递这个模板类的模板参数,还是使用以上的例子,我们现在必须得这样使用这个模板类 myfunction<decltype(func), int (int,int)> f;
, 其中第一个模板参数Functor
, 需要我们自己显示指定;这显然会加大使用负担,且与std::function<int (int, int)>
的形式不符;
结合以上的思考,将第一个模板参数Functor
放到构造函数中去,由编译器进行推导,而不是定义myfunction
时显示指定类型,这是比较合理的,也更符合使用习惯;所以我们还是要在代码段1的基础上进行改进;
现在问题变成了: 怎样才能让代码段1支持存储Functor
类型的成员变量呢?
有了以上的思考,我们知道,将模板参数的作用域从函数提升到类中,就可以扩大可见性
我们可以构造一个辅助类,将构造函数的模板参数变成辅助类的类模板参数
,这样就可以将辅助类的类模板参数存储在辅助类中,然后想办法在myfunction
中存储辅助类的指针或对象,在myfunction
重载的()
中调用存储在辅助类中的可调用函数,代码如下:
template <typename Ret, typename... Args>
struct myfunction
{
template <typename Functor>
myfunction(Functor&& func)
{
m_pCallAble = new CallAble<Functor>(std::forward<Functor>(func));
}
Ret operator()(Args&&... args)
{
if (m_pCallAble != nullptr)
{
return m_pCallAble->invoke(std::forward<Args>(args)...);
}
}
//辅助类基类
struct ICallAble
{
virtual ~ICallAble() = default;
virtual Ret invoke(Args&&... args) = 0;
};
//辅助类, 用于存储myfunction构造函数中的 可执行函数
template <typename Functor>
struct CallAble
{
CallAble(Functor&& func) : m_func(func) {}
Ret invoke(Args&&... args) override
{
return m_func(std::forward<Args>(args)...);
}
Functor m_func;
};
ICallAble* m_pCallAble = nullptr;
};
OK,以上代码我们成功将myfunction
构造函数的参数存储到了 m_pCallAble
变量中,到这里为止,我们这个myfunction
类已经可以包装 普通函数 lambda表达式 重载了()的类对象 类静态函数;
但是还无法包装类成员函数,我们观察最初的例子中的 形式3
,不难发现这种形式的构造函数需要传递一个可调用函数
,以及一个类对象
, 所以我们还需要给myfunction
加一个重载的构造函数
template<typename Functor, typename Class>
myfunction(Functor&& func, Class* obj)
{
}
当然我们可以继续使用 m_pCallAble
成员变量,此时 new
的就不再是 CallAble
类对象了,因为这个类只能存储Functor
模板参数,无法存储Class
模板参数;所以我们继续派生自 ICallAble
,写一个可以存储这两个参数的模板类
//重载的构造函数
template<typename Functor, typename Class>
myfunction(Functor&& func, Class* obj)
{
m_pCallAble = new MemCallAble<Functor, Class>(std::forward<Functor>(func), obj);
}
//包装类成员函数的派生类
template <typename Functor, typename Class>
struct MemCallAble : public ICallAble
{
MemCallAble(Functor&& func, Class* obj): m_func(std::forward<Functor>(func)), m_obj(obj)
{
}
Ret invoke(Args&&... args) override
{
return (m_obj->*m_func)(std::forward<Args>(args)...);
}
Functor m_func;
Class* m_obj = nullptr;
};
这样我们的myfunction
就可以类似形式3
,来包装类成员函数了,到此实现了基本功能, 贴出以上思考后的代码:
template <typename T>
class myfunction;
template <typename Ret, typename... Args>
class myfunction<Ret(Args...)>
{
public:
myfunction() {}
template <typename Function>
myfunction(Function&& func) : m_ptr { new CallAble<Function>(std::forward<Function>(func)) } {}
template <typename Function, typename Class>
myfunction(Function&& func, Class* obj) : m_ptr { new MemCallAble<Function, Class>(std::forward<Function>(func), obj) } {}
myfunction(const myfunction& rhs) { m_ptr = rhs.m_ptr->clone(); }
myfunction& operator=(const myfunction& rhs)
{
if (this == &rhs)
{
return *this;
}
delete m_ptr;
m_ptr = rhs.m_ptr ? rhs.m_ptr->clone() : nullptr;
return *this;
}
~myfunction()
{
if (m_ptr)
{
delete m_ptr;
}
}
myfunction(myfunction&& rhs)
{
if (m_ptr )
{
delete m_ptr;
}
m_ptr = rhs.m_ptr;
rhs.m_ptr = nullptr;
}
Ret operator()(Args... args) { return m_ptr->invoke(std::forward<Args>(args)...); }
private:
//辅助类基类,使用继承方式, 擦除掉构造函数的模板参数;这样可以在此类中定义 ICallAble 指针
struct ICallAble
{
virtual Ret invoke(Args&&... args) = 0;
virtual ICallAble* clone() = 0;
virtual ~ICallAble() = default;
};
//包装一般可调用对象, 普通函数、lambda表达式、类静态函数、重载了()的类对象
template <typename Function>
struct CallAble : public ICallAble
{
CallAble(Function&& func) : m_real_func(std::forward<Function>(func)) {}
ICallAble* clone() override { return new CallAble<Function>(std::forward<Function>(m_real_func)); }
Ret invoke(Args&&... args) override
{
return m_real_func(std::forward<Args>(args)...);
}
~CallAble() = default;
Function m_real_func = nullptr; //真正执行的函数
};
//包装类成员函数,类成员函数需要类对象才能进行调用,所以比普通版本多一个Class类型模板参数
template<typename Function, typename Class>
struct MemCallAble : public ICallAble
{
MemCallAble(Function&& func, Class* obj) : m_real_func(std::forward<Function>(func)), m_obj(obj)
{
}
ICallAble* clone() override { return new MemCallAble<Function, Class>(std::forward<Function>(m_real_func), m_obj); }
Ret invoke(Args&&... args) override
{
return (m_obj->*m_real_func)(std::forward<Args>(args)...);
}
~MemCallAble() = default;
Function m_real_func = nullptr; //真正执行的函数
Class* m_obj = nullptr; //类对象
};
private:
ICallAble* m_ptr = nullptr;
};