libco源码解析(9) closure实现
libco源码解析(1) 协程运行与基本结构
libco源码解析(2) 创建协程,co_create
libco源码解析(3) 协程执行,co_resume
libco源码解析(4) 协程切换,coctx_make与coctx_swap
libco源码解析(5) poll
libco源码解析(6) co_eventloop
libco源码解析(7) read,write与条件变量
libco源码解析(8) hook机制探究
libco源码解析(9) closure实现
引言
libco中提供了一份闭包的实现,根据这份实现,我们不但可以把这个闭包用于线程,也可以用于协程。因为libco中的函数表示通常使用函数指针,而不是std::function,所以没办法使用C++自带的闭包机制,所以实现了这么一个简易的闭包。不过说是简易,只有区区100行代码,不过这些个宏函数确实是看的人头皮发麻,写出这些代码的人估计也是那种传说中的C程序员了。
在co_closure.h
中原代码注释把整个文件分为了六个部分,我们逐段来讲解:
基类
struct stCoClosure_t
{
public:
virtual void exec() = 0;
};
这一部分是一个简单的基类,实际上libco的闭包原理就是根据宏函数去展开生成一个类,整个类中包含了我们传入的参数和函数,而且是继承这个stCoClosure_t
的,我们只需要在协程内部或者线程内部直接执行这个exec就可以达到闭包的效果了。
1.1 comac_argc
#define comac_get_args_cnt( ... ) comac_arg_n( __VA_ARGS__ )
#define comac_arg_n( _0,_1,_2,_3,_4,_5,_6,_7,N,...) N
#define comac_args_seqs() 7,6,5,4,3,2,1,0
#define comac_join_1( x,y ) x##y
#define comac_argc( ... ) comac_get_args_cnt( 0,##__VA_ARGS__,comac_args_seqs() )
#define comac_join( x,y) comac_join_1( x,y )
这些函数就是为了完成一个功能,即求出传入的可变参数的个数,当然从源码中我们也可以看出来最多支持7个参数,也就是闭包内我们最多传入7个参数,否则就会出现问题。
我们对于comac_argc
的展开举一个简单的例子:
- comac_argc(A, B, C)
- comac_get_args_cnt( 0, A, B, C, 7, 6, 5, 4, 3, 2, 1, 0)
- comac_arg_n(0, A, B, C, 7, 6, 5, 4,
3
, 2, 1, 0) - 上面的comac_arg_n可以推导出3,即可变参数的个数。
这里有两个知识点:
__VA_ARGS__
:代表全部的可变参数,相当于一个宏替换。- comac_argc的参数中为什么要在
__VA_ARGS__
前加##?
在gcc中,前缀##有一个特殊约定,即当##arg前面是逗号(,)时,如果arg为空,则##之前的逗号(,)将会被自动省去。
因此,当comac_argc()不填写任何参数时,宏将会被按照以下方式展开:
comac_argc( ) -> comac_get_args_cnt( 0, 7,6,5,4,3,2,1,0 ) -> comac_arg_n( 0, 7,6,5,4,3,2,1,0 ) -> 0
但是,对于C++11(-std=c++11),如果##arg中的arg为空,则##arg将会被默认转换为空字符串(""),此时,宏将会按照下面的方式展开:
comac_argc( ) -> comac_get_args_cnt( 0, “”, 7,6,5,4,3,2,1,0 ) -> comac_arg_n( 0, “”, 7,6,5,4,3,2,1,0 ) -> 1
1.2 repeat
#define repeat_0( fun,a,... )
#define repeat_1( fun,a,... ) fun( 1,a,__VA_ARGS__ ) repeat_0( fun,__VA_ARGS__ )
#define repeat_2( fun,a,... ) fun( 2,a,__VA_ARGS__ ) repeat_1( fun,__VA_ARGS__ )
#define repeat_3( fun,a,... ) fun( 3,a,__VA_ARGS__ ) repeat_2( fun,__VA_ARGS__ )
#define repeat_4( fun,a,... ) fun( 4,a,__VA_ARGS__ ) repeat_3( fun,__VA_ARGS__ )
#define repeat_5( fun,a,... ) fun( 5,a,__VA_ARGS__ ) repeat_4( fun,__VA_ARGS__ )
#define repeat_6( fun,a,... ) fun( 6,a,__VA_ARGS__ ) repeat_5( fun,__VA_ARGS__ )
#define repeat( n,fun,... ) comac_join( repeat_,n )( fun,__VA_ARGS__)
这一部分的功能是对闭包传入的参数的类型声明,根据不同的参数数量调用不同的函数,其实是一个递归的过程。
2. implement
#if __cplusplus <= 199711L
#define decl_typeof( i,a,... ) typedef typeof( a ) typeof_##a;
#else
#define decl_typeof( i,a,... ) typedef decltype( a ) typeof_##a;
#endif
#define impl_typeof( i,a,... ) typeof_##a & a;
#define impl_typeof_cpy( i,a,... ) typeof_##a a;
#define con_param_typeof( i,a,... ) typeof_##a & a##r,
#define param_init_typeof( i,a,... ) a(a##r),
这一部分的函数主要与参数的类型相关,可以根据传入的参数自动生成对类型的推导,可以用于函数参数的设定。我们一一来看看吧:
decl_typeof
:使用typeod_a来代表传入参数的类型,举个例子:
string str = "hello world!";
decl_typeof( i, str)
-> typedef decltype( str ) typeof_str
我们可以看出此时用typeof_str可以代替str的参数类型。
impl_typeof
:生成一个对于特定类型的引用,举个例子:
string str = "hello world!";
impl_typeof( i, str)
-> typeof_str & str
我们可以看到生成了一个引用变量,只不过还没有赋值。
impl_typeof_cpy
:都是申请一个变量。和引用差不多,只不过生成的是拷贝而已。con_param_typeof
:生成函数的参数。param_init_typeof
:生成函数初始化列表。
2.1 reference
#define co_ref( name,... )\
repeat( comac_argc(__VA_ARGS__) ,decl_typeof,__VA_ARGS__ )\
class type_##name\
{\
public:\
repeat( comac_argc(__VA_ARGS__) ,impl_typeof,__VA_ARGS__ )\
int _member_cnt;\
type_##name( \
repeat( comac_argc(__VA_ARGS__),con_param_typeof,__VA_ARGS__ ) ... ): \
repeat( comac_argc(__VA_ARGS__),param_init_typeof,__VA_ARGS__ ) _member_cnt(comac_argc(__VA_ARGS__)) \
{}\
} name( __VA_ARGS__ ) ;
这里的co_ref
所做的事情其实就是根据闭包传入的参数生成一个类,这个类持有了对于所有参数的引用,并可以推导出参数的数量。
我们看一个例子就可以清楚的理解其过程:
int number = 1024;
string str = "hello world1";
co_ref(ref, number, str)
typedef decltype( str ) typeof_str;
typedef decltype( number ) typeof_number;
class type_ref
{
public:
typeof_str& str;
typeof_number& number;
type_ref(typeof_str& str_r, typeof_number& number_r):
str(str_r), number(number_r)
{}
}ref(number, str);
一个函数在经过推导以后成了一个包含所有参数的类,就是这么神奇。
2.2 function
#define co_func(name,...)\
repeat( comac_argc(__VA_ARGS__) ,decl_typeof,__VA_ARGS__ )\
class name:public stCoClosure_t\
{\
public:\
repeat( comac_argc(__VA_ARGS__) ,impl_typeof_cpy,__VA_ARGS__ )\
int _member_cnt;\
public:\
name( repeat( comac_argc(__VA_ARGS__),con_param_typeof,__VA_ARGS__ ) ... ): \
repeat( comac_argc(__VA_ARGS__),param_init_typeof,__VA_ARGS__ ) _member_cnt(comac_argc(__VA_ARGS__))\
{}\
void exec()
#define co_func_end }
我们像上面一样,举一个简单的例子:
int number = 1024;
string str = "hello world1";
co_ref(ref, number, str)
co_func(f, ref)
{
printf("hello, world!");
}
co_func_end;
typedef decltype( str ) typeof_str;
typedef decltype( number ) typeof_number;
class type_ref
{
public:
typeof_str& str;
typeof_number& number;
type_ref(typeof_str& str_r, typeof_number& number_r):
str(str_r), number(number_r)
{}
}ref(number, str);
typedef typeof(ref) typeof_ref;
class f : public stCoClosure_t
{
public:
typeof_ref ref;
int _member_cnt;
public:
f(typeof_ref & refr, ...) : ref(refr), _member_cnt(1)
{
}
void exec()
{
printf("hello, world!\n");
}
};
此时我们就得到了一个类,其中的exec
函数就是我们所注册的函数,我们不但可以使用闭包中的参数,也可以在使用函数时传入参数,这样一个闭包就完成了,当然要使用的话我们只需要在协程或者线程中执行这个类中的exec函数即可。
总结
对于libco中closure的实现着实让人开了眼界,但是我认为,这种纯用宏的编程确实一般人一辈子也遇不到几次,所以至少现在我觉得学了和没学都是差不多的。我是因为为了保证libco源码分析系列的完整性去学习了这个,一般来说还是看个人兴趣喽。