本节我们来实现一个简易的std::function
我们知道std::function
是用来包装可调用对象
的,在C++
中,可调用对象包括 普通函数、lambda表达式、重载了()操作符的类对象、类静态函数、类成员函数这几类。
C++程序的编译顺序:
预处理(xxx.i)
编译(xxx.s)
汇编(xxx.obj)
链接(xxx.exe / linux可执行文件)
;
模板类(函数)的实例化,是在 预处理之后,编译之前;所以模板并不是真正可执行的C++代码,应该将它理解成一个模具
,我们使用什么样的材料(模板参数),就会生成什么样的代码;
1.模板函数的模板参数是可以使用编译器自动推导,而模板类的模板参数,却需要显示指定,无法使用自动推导(C++17之后可推导)
使用模板函数自动推导模板参数的前提是: 模板函数中的函数参数
,必须要和模板参数
有一定的联系,才能利用函数参数的类型,推导出模板参数的类型
2.模板类(函数)的模板参数,其实只是一个类型占位符;我们可以赋予这个占位符任何意义,模板类(函数)的实现也是基于这个
我们赋予的意义
来进行编写的;所以在使用模板类(函数)时,只要传入的模板参数能满足我们赋予的意义
,这个模板类(函数)就能正常运行**
首先回顾一下 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表达式),所以我们这里可以将构造函数写成一个模板函数,我们传递一个可执行的函数
进来,无论是普通函数
还是lambda表达式
,模板参数类型交给编译器去推导就行了
结合我们以上发现的特点,最容易想到形式是:
模板参数类型为 返回值类型
参数包
, 构造函数为模板函数,构造函数参数为一个可执行的函数
; 然后重载 ()
操作符,将参数包转发到构造函数传递的可调用函数
中去,执行一下该函数即可;
//代码段1
template <typename Ret, typename... Args>
struct myfunction<Ret(Args...)>
{
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
模板参数从构造函数的模板参数
,移动到类模板参数中
, 这样就扩大了模板参数的可见范围,则可以在类中定义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) //希望此处使用编译器自动推导,将Functor类型存储在 CallAble模板类中
{
m_pCallAble = new CallAble<Functor>(std::forward<Functor>(func));
}
Ret operator()(Args&&... args)
{
if (m_pCallAble != nullptr