目录
前言
第一版
第二版
第三版
总结
他山之石
在《std::function从实践到原理》中我们分析了std::function的实现原理,但这只是纸上谈兵。要想理解为什么这么实现,最好的办法还是想想要是自己手写一个要怎么实现。本文不想直接呈现最终版本,因为那样读者看不到某段代码是为了什么才那么写。我会搭建好几版,争取把所想所思都体现出来。
我们先不考虑复制构造函数,也不考虑移动构造函数,只考虑最普通的构造函数, 一共两种类型(functor type)可以赋值给Myfunction: lambda表达式、函数指针。仿函数与lambda异曲同工,略。这两种类型要么通过类模板参数要么通过构造函数的模板参数传进来,分别形如:
template
class Myfunction
//构造函数
template
Myfunction(_Functor __f){
。。。
}
第一种要求用户必须这样使用:Myfunction<..., lambda0>, 但实际用户是给不出lambda表达式对应的类的(由编译器给出), 除非用decltype; 但即使给出来了,也不符合一贯的std::function<函数声明>的用法。所以只能是第二种。然后还有个需求:用户传过来的lambda对象或者函数指针得copy一份,这样以后才能调用到它们(重载函数operator ()()中调用)所以这个Myfunction大概得长这样:
template
class Myfunction
{
public:
template
Myfunction(_Functor __f){
f = new _Functor(std::move(__f));
}
_Res operator()(_ArgTypes... __args) const{
return (*f)(std::forward<_ArgTypes>(__args)...);
}
private:
_Functor* f;
};
但这样有个大问题:_Functor作用域只在构造函数内,_Functor* f是编译不过的。所以f要承接lambda对象,也要承接函数指针,那只能是万能指针void* f或char* f了。
如果f成了void*类型,operator()()中还是得知道f原本的类型,因为void* f不是callable的,你不能写 f(), 得把f转换成原来的类型如lambda或函数指针。_Functor只在构造函数中有,但operator()()却要使用,怎么办?
要解决这个问题,我们就得学一学std::function了。引入一个函数指针,它里面保留_Functor这个信息。
template
class _MyFunction_handler
{
public:
statc _Res _Function_handler_invoke(void* _Any_data, _ArgTypes... __args){
return (*(_Functor*)_Any_data)(std::forward<_ArgTypes>(__args)...);
}
};
template
class MyFunction;
template
class MyFunction<_Res(_ArgTypes...)>
{
public:
template
MyFunction(_Functor __f){
f = new _Functor(std::move(__f));
_M_invoker = &_MyFunction_handler<_Res,_Functor,_ArgTypes...>::_Function_handler_invoke;
}
_Res operator()(_ArgTypes... __args) const{
return (*_M_invoker)(f, std::forward<_ArgTypes>(__args)...);
}
private:
void* f;
typedef _Res (*_Invoker_type)(void* _Any_data, _ArgTypes...);
_Invoker_type _M_invoker;
};
_Function_handler_invoke与_M_invoker的函数签名都一样:_Res (void*, _ArgTypes...), 只不过_Function_handler_invoke里面保留了_Functor信息,可以把void* f转换成原来的样子。当然_Function_handler_invoke也可以直接写在Myfunction内。
_M_invoker是一个桥梁,把构造和调用连接了起来(_Functor方面) 。写几个测试用例试一试:
int gAdd(int a, int b){
return a+b;
}
int main(){
MyFunction f1 = [](int a,int b)->int {return a+b;};
int res = f1(1,2);
std::cout< f2 = gAdd;
int res2 = f2(3,4);
std::cout<
能走通了!
显然有个问题:new的东西没释放。依照_M_invoker再搞一个_M_destroy。
#include
template
class _MyFunction_handler
{
public:
static _Res _Function_handler_invoke(void* _Any_data, _ArgTypes... __args){
return (*(_Functor*)_Any_data)(std::forward<_ArgTypes>(__args)...);
}
static void _Function_handler_destroy(void* _Any_data){
//((_Functor*)_Any_data)->~_Functor();
delete ((_Functor*)_Any_data);
}
};
template
class MyFunction;
template
class MyFunction<_Res(_ArgTypes...)>
{
public:
template
MyFunction(_Functor __f){
f = new _Functor(std::move(__f));
_M_invoker = &_MyFunction_handler<_Res,_Functor,_ArgTypes...>::_Function_handler_invoke;
_M_destroy = &_MyFunction_handler<_Res,_Functor,_ArgTypes...>::_Function_handler_destroy;
}
_Res operator()(_ArgTypes... __args) const{
return (*_M_invoker)(f, std::forward<_ArgTypes>(__args)...);
}
~MyFunction(){
(*_M_destroy)(f);
}
private:
void* f;
typedef _Res (*_Invoker_type)(void* _Any_data, _ArgTypes...);
_Invoker_type _M_invoker;
typedef void (*_Destroy_type)(void* _Any_data);
_Destroy_type _M_destroy;
};
int gAdd(int a, int b){
return a+b;
}
int main(){
MyFunction f1 = [](int a,int b)->int {return a+b;};
int res = f1(1,2);
std::cout< f2 = gAdd;
int res2 = f2(3,4);
std::cout<
[mzhai@~]$ g++ myfunction.cpp -std=c++11 -g
[mzhai@~]$ ./a.out
3
7
[mzhai@c++11]$ valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt1 ./a.out
3
7
valgrind测试也没有内存泄漏。读者如想看看模板是怎么展开的,只需把上面的代码拷贝到c++ insights 即可。
完美?NO,还没有处理给Myfunction赋值nullptr的情况。 还没有写各种各样的构造函数, 其中copy构造函数也需要知道原来的类型(lambda类型还是函数指针?),这样就又得起一个类似_M_invoker/_M_destroy的成员,std::function中的_M_manager就是为copy function和析构而引入的,只不过它最后有一个参数std::_Manager_operation用来区分是copy构造还是析构。
后来我在Stack Overflow上看到一个别人写的std::function. 大体思想一样,贴在下面:
#include
#include
template
class function;
template
class function
{
// function pointer types for the type-erasure behaviors
// all these char* parameters are actually casted from some functor type
typedef R (*invoke_fn_t)(char*, Args&&...);
typedef void (*construct_fn_t)(char*, char*);
typedef void (*destroy_fn_t)(char*);
// type-aware generic functions for invoking
// the specialization of these functions won't be capable with
// the above function pointer types, so we need some cast
template
static R invoke_fn(Functor* fn, Args&&... args)
{
return (*fn)(std::forward(args)...);
}
template
static void construct_fn(Functor* construct_dst, Functor* construct_src)
{
// the functor type must be copy-constructible
new (construct_dst) Functor(*construct_src);
}
template
static void destroy_fn(Functor* f)
{
f->~Functor();
}
// these pointers are storing behaviors
invoke_fn_t invoke_f;
construct_fn_t construct_f;
destroy_fn_t destroy_f;
// erase the type of any functor and store it into a char*
// so the storage size should be obtained as well
std::unique_ptr data_ptr;
size_t data_size;
public:
function()
: invoke_f(nullptr)
, construct_f(nullptr)
, destroy_f(nullptr)
, data_ptr(nullptr)
, data_size(0)
{}
// construct from any functor type
template
function(Functor f)
// specialize functions and erase their type info by casting
: invoke_f(reinterpret_cast(invoke_fn))
, construct_f(reinterpret_cast(construct_fn))
, destroy_f(reinterpret_cast(destroy_fn))
, data_ptr(new char[sizeof(Functor)])
, data_size(sizeof(Functor))
{
// copy the functor to internal storage
this->construct_f(this->data_ptr.get(), reinterpret_cast(&f));
}
// copy constructor
function(function const& rhs)
: invoke_f(rhs.invoke_f)
, construct_f(rhs.construct_f)
, destroy_f(rhs.destroy_f)
, data_size(rhs.data_size)
{
if (this->invoke_f) {
// when the source is not a null function, copy its internal functor
this->data_ptr.reset(new char[this->data_size]);
this->construct_f(this->data_ptr.get(), rhs.data_ptr.get());
}
}
~function()
{
if (data_ptr != nullptr) {
this->destroy_f(this->data_ptr.get());
}
}
// other constructors, from nullptr, from function pointers
R operator()(Args&&... args)
{
return this->invoke_f(this->data_ptr.get(), std::forward(args)...);
}
};
// examples
int main()
{
int i = 0;
auto fn = [i](std::string const& s) mutable
{
std::cout << ++i << ". " << s << std::endl;
};
fn("first"); // 1. first
fn("second"); // 2. second
// construct from lambda
::function f(fn);
f("third"); // 3. third
// copy from another function
::function g(f);
f("forth - f"); // 4. forth - f
g("forth - g"); // 4. forth - g
// capture and copy non-trivial types like std::string
std::string x("xxxx");
::function h([x]() { std::cout << x << std::endl; });
h();
::function k(h);
k();
return 0;
}