目录
一、本文目的
std::function和std::bind是C++11新增特性,可以非常方便实现函数的回调,且非常安全。对于如何使用std::function和std::bind不是本文的重点,本文目的是在于如何自己手动实现std::function和std::bind,只有自己手动实现了才会了解其中的原理,同时实现这两个函数有助于掌握C++模板使用技巧,如模板特例化、偏特例化变参模板等。
二、std::function和std::bind简介
std::function和std::bind在#include<functional>中,std::function是对可调用函数的封装,保存了函数以及参数,也就是闭包。std::bind是可以将函数和参数进行绑定,打包成binder对象,binder对象可以转化为function对象,如下代码:
// TestBaisc.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "_function.h"
#include <iostream>
#include <functional>
class Print
{
public:
Print() {}
virtual ~Print() {}
int draw(int a, int b, int c)
{
std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
return 0;
}
};
int print(int a, int b, int c)
{
std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
return 0;
}
int main()
{
std::function<int()> _printf = std::bind(&print, 1, 2, 3); // 绑定普通函数
_printf();
Print pt;
std::function<int()> _mprintf = std::bind(&Print::draw, &pt, 1, 2, 3); // 绑定成员函数
_mprintf();
std::function<int()> _labprintf = std::bind([=](int a,int b,int c) { // 绑定lambda表达式
std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
return 0;
}, 1, 2, 3);
_labprintf();
system("pause");
return 0;
}
std::bind可以将将函数和参数打包为function对象f,通过f()延后执行。这种方式可以非常灵活的处理回调函数。上面代码演示事先绑定所以参数,也可以不绑定参数或者部分参数,而是在调用时传入所需要的参数。
int main()
{
std::function<int(int,int)> _printf = std::bind(&print, std::placeholders::_1, 2, std::placeholders::_2); // 第1、3个参数需要出入,第2个固定
_printf(1,3);
system("pause");
return 0;
}
三、简单实现binder类
std::bind绑定函数和参数返回的是_Binder对象,可以通过vs查看(鼠标移到std::bind上),如下:
因此有理由相信_Binder肯定保存了函数和参数包,事实上确实如此,可以查看functional代码。因为functional代码非常混乱,在这里补贴出。
要想实现std::bind先必须实现_Binder类,该类用于保存函数和参数包。为了简化这里暂时只保存函数,不保存参数,参数保存问题下面再解决。
为了不冲突,这里取_Binder类名为_binder_,先用它保存一般函数(普通函数、Lamda函数、静态函数)。1)显然_binder_是一个模板类形参为Fx用于传入函数,2)类模板中有一个成员Fx m_f用于保存函数。3)需要时实现一个仿函数用于传入参数包,用m_f进行调用,如下:
template<typename Fx>
class _binder_
{
public:
_binder_(Fx f) :m_f(f) {}
template<typename... Args>
auto operator()(Args... args) // 仿函数为函数模板,传入参数包
{
return (*m_f)(args...); // 函数调用参数包
}
private:
Fx m_f;
};
可以简单使用上面的_binder_类,如下:
int main()
{
_binder_<int(*)(int, int, int)> f = print;
f(1, 2, 3);
system("pause");
return 0;
}
上面使用_binder_类实现了保存一般函数的目的,那么类成员函数如何保存呢?_binder_可以保存类成员函数,但是无法实现调用,因为类成员函数调用是需要对象进行调用的,而_binder_中的(*m_f)(args...)并没有和对象挂钩,如下:
int main()
{
Print pt;
_binder_<int(Print::*)(int, int, int)> f2 = &Print::draw;
// f2(); // 错误
system("pause");
return 0;
}
所以,我们需要另外实现一个_binder_模板用于处理成员函数的保存。假设为_mbinder_,显然_mbinder_类模板参数除了Fx外,还需要T类类型参数用于保存对象。_mbiner_实现如下:
template<typename Fx, typename T>
class _mbinder_
{
public:
_mbinder_(Fx f, T t) :m_f(f),m_t(t) {}
template<typename... Args>
auto operator()(Args... args) // 仿函数为函数模板,传入参数包
{
return (m_t->*m_f)(args...); // 函数调用参数包
}
private:
Fx m_f;
T m_t;
};
同样可以使用_mbinder_用于保存类成员函数,如下:
int main()
{
Print pt;
_mbinder_<int(Print::*)(int, int, int), Print> f2(&Print::draw, &pt);
f2(1,2,3);
system("pause");
return 0;
}
四、简单实现std::bind函数
std::bind函数返回的是_Binder对象,由于一般函数和成员函数的binder不一样,那么需要处理,在绑定一般函数时返回的是_binder_,绑定成员函数返回的是_mbinder_,这里我们分别提供返回_binder_和_mbinder_的函数模板,如下:
// 一般函数bind
template<typename Fx>
auto _bind(Fx f)
{
return _binder_<Fx>(f);
}
// 成员函数bind
template<typename Fx, typename T>
auto _bind(Fx f, T *t)
{
return _mbinder_<Fx, T>(f, t);
}
现在我们可以利用_bind实现简单对一般函数和类成员函数的绑定调用,如下:
int main()
{
auto f = _bind(&print);
f(1, 2, 3);
Print pt;
auto f2 = _bind(&Print::draw, &pt);
f2(1, 2, 3);
system("pause");
return 0;
}
五、简单实现std::function(难点)
我们已经实现了_Binder,采用auto f = _bind(...)方式可以直接调用f(...),按理说已经差不多了,但是还不够,_bind的返回有点复杂,如上面代码,f的类型为int(*)(int,int,int),f2的类型为int(Print::*)(int,int,int)。如果我们想在另外一个类中实现f2的回调,那么必须使用int(Print::*)(int,int,int)保存成员函数指针,那么这个类就依赖了Print类;如果我们需要回调很多类形如int(class::*)(int,int,int)的函数,那么就需要分别添加各个类的int(class::*)(int,int,int)函数指针,然后在回函数中添加分别调用。所以很有必要将int(*)(int,int,int)、int(class::*)(int,int,int)转化为同一种形式,这种形式就是std::function。
std::function形式为std::function<R(Args...)>,R为返回值,Args...为参数包,所以std::function模板形式为template<typename Fx>,Fx就是R(Args...)函数形式。std::bind返回值为_Binder,但是可以将_Binder对象赋给std::function对象,显然std::function构造函数中实现了对_Binder的转化。我们实现一个简单的_function如下:
template<typename Fx>
class _function
{
public:
template<typename _binderType>
_function(_binderType binder)
{
}
template<typename... Args>
auto operator()(Args... args)
{
// return binder(args...); // 希望如此
}
private:
};
仿函数里面,我们希望调用binder(args...),由于构造函数和仿函数是异步的,所以必须想办法在构造函数中保存binder。最先想到的办法是在_function中添加_binderType m_binder,但是_binderType并没有作为模板形参,所以无法这样做,那么是否可以将_binderType作为模板形参呢?如果将_binderType作为形参即template<typename Fx,typename _binderType> class _function,那么_function的形式就不能统一。
那么如何保存binder呢?这里有一个技巧,虽然_function不能通过成员函数保存binder,但是可以通过另外一个类去保存binder呢?定义类binder_wrapper_impl,该类为接口类,接口类中不含有binder,binder的保存放在binder_wrapper中实现,binder_wrapper继承binder_wrapper_impl,那么_function中可以直接包含binder_wrapper_impl指针,创建时使用binder_wrapper进行创建,并传入binder。 在仿函数中通过虚接口call进行调用,在binder_wrapper中实现虚接口call,使用保存下来的binder进行调用。实现大体如下:
// 错误代码
class binder_wrapper_impl
{
public:
template<typename... Args>
virtual auto call(Args... args)
{
return 1;
}
};
template<typename _binderType>
class binder_wrapper:public binder_wrapper_impl
{
public:
binder_wrapper(_binderType binder) :m_binder(binder) {}
template<typename... Args>
virtual auto call(Args... args)
{
return m_binder(args...);
}
private:
_binderType m_binder;
};
template<typename Fx>
class _function
{
public:
template<typename _binderType>
_function(_binderType binder)
{
m_binder = new binder_wrapper<_binderType>(binder);
}
virtual ~_function() {
delete m_binder;
}
template<typename... Args>
auto operator()(Args... args)
{
return m_binder->call(args...);
}
private:
binder_wrapper_impl *m_binder = nullptr;
};
上面代码不能编译通过,原因时类函数模板不能为虚函数,故必须将模板形参提到类中,即binder_wrapper_impl为类模板,所以binder_wrapper_impl和binder_wrapper修改为如下:
template<typename... Args>
class binder_wrapper_impl
{
public:
virtual auto call(Args... args) { return 0; }
};
template<typename _binderType, typename... Args>
class binder_wrapper:public binder_wrapper_impl<Args...>
{
public:
binder_wrapper(_binderType binder) :m_binder(binder) {}
virtual auto call(Args... args)
{
return m_binder(args...);
}
private:
_binderType m_binder;
};
上面代码还是无法编译通过,原因是虚函数不能含有auto返回类型,因此binder_wrapper_impl模板形参还必须包含返回类型,所以上面代码需要改为如下形式:
template<typename R, typename... Args>
class binder_wrapper_impl
{
public:
virtual R call(Args... args) { return R(); }
};
template<typename _binderType, typename R, typename... Args>
class binder_wrapper:public binder_wrapper_impl<R, Args...>
{
public:
binder_wrapper(_binderType binder) :m_binder(binder) {}
virtual R call(Args... args)
{
return m_binder(args...);
}
private:
_binderType m_binder;
};
这就造成了_functiom_binder的形式定义必须为:binder_wrapper_impl<R, Args...> m_binder;所以_function模板形参必须有返回值类和参数包类型,所以_function修改后如下:
// 泛化版本
template<typename R, typename... Args>
class _function;
// 偏特例化
template<typename R, typename... Args>
class _function<R(Args...)>
{
public:
template<typename _binderType>
_function(_binderType binder)
{
m_binder = new binder_wrapper<_binderType, R, Args...>(binder);
}
virtual ~_function() {
delete m_binder;
}
auto operator()(Args... args)
{
return m_binder->call(args...);
}
private:
binder_wrapper_impl<R, Args...> *m_binder = nullptr;
};
这里用到了类模板的偏特例化。
完整代码如下:
template<typename Fx>
class _binder_
{
public:
_binder_(Fx f) :m_f(f) {}
template<typename... Args>
auto operator()(Args... args) // 仿函数为函数模板,传入参数包
{
return (*m_f)(args...); // 函数调用参数包
}
private:
Fx m_f;
};
template<typename Fx, typename T>
class _mbinder_
{
public:
_mbinder_(Fx f, T *t) :m_f(f),m_t(t) {}
template<typename... Args>
auto operator()(Args... args) // 仿函数为函数模板,传入参数包
{
return (m_t->*m_f)(args...); // 函数调用参数包
}
private:
Fx m_f;
T *m_t;
};
//
// 一般函数bind
template<typename Fx>
auto _bind(Fx f)
{
return _binder_<Fx>(f);
}
// 成员函数bind
template<typename Fx, typename T>
auto _bind(Fx f, T *t)
{
return _mbinder_<Fx, T>(f, t);
}
//
template<typename R, typename... Args>
class binder_wrapper_impl
{
public:
virtual R call(Args... args) { return R(); }
};
template<typename _binderType, typename R, typename... Args>
class binder_wrapper:public binder_wrapper_impl<R, Args...>
{
public:
binder_wrapper(_binderType binder) :m_binder(binder) {}
virtual R call(Args... args)
{
return m_binder(args...);
}
private:
_binderType m_binder;
};
// 泛化版本
template<typename R, typename... Args>
class _function;
// 偏特例化
template<typename R, typename... Args>
class _function<R(Args...)>
{
public:
template<typename _binderType>
_function(_binderType binder)
{
m_binder = new binder_wrapper<_binderType, R, Args...>(binder);
}
virtual ~_function() {
delete m_binder;
}
auto operator()(Args... args)
{
return m_binder->call(args...);
}
private:
binder_wrapper_impl<R, Args...> *m_binder = nullptr;
};
可以实现调用,如下:
int main()
{
_function<int(int,int,int)> f = _bind(&print);
f(1, 2, 3);
Print pt;
_function<int(int,int,int)> f2 = _bind(&Print::draw, &pt);
f2(1, 2, 3);
_function<int(int, int, int)> f3 = _bind(&[=](int a, int b, int c) {
std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
return 0;
});
f3(1, 2, 3);
system("pause");
return 0;
}
总结:在讲述如何实现std::function时,我并未直接写出最终实现代码,而是一步步引导如何实现,否则读者可能会觉得如此实现谁能想的到?实现上只要写多接触多了,其中的技巧就会熟练。可如何保存binder,实现延期回调是整个最难地方,用到知识点和技巧总结如下:
- 在_function构造函数中实现_Binder的转化;
- 通过间接类实现binder的保存,通过继承机制,避免_function模板形参中包含_binderType;
- 类模板的偏特例化使用。
接下来需要实现_Binder参数包的保存,由于参数包的保存技巧性非常大、也非常难,将在下一篇解决该问题。