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的展开举一个简单的例子:

  1. comac_argc(A, B, C)
  2. comac_get_args_cnt( 0, A, B, C, 7, 6, 5, 4, 3, 2, 1, 0)
  3. comac_arg_n(0, A, B, C, 7, 6, 5, 4, 3, 2, 1, 0)
  4. 上面的comac_arg_n可以推导出3,即可变参数的个数。

这里有两个知识点:

  1. __VA_ARGS__:代表全部的可变参数,相当于一个宏替换。
  2. 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),

这一部分的函数主要与参数的类型相关,可以根据传入的参数自动生成对类型的推导,可以用于函数参数的设定。我们一一来看看吧:

  1. decl_typeof:使用typeod_a来代表传入参数的类型,举个例子:
string str = "hello world!";
decl_typeof( i, str)
-> typedef decltype( str ) typeof_str

我们可以看出此时用typeof_str可以代替str的参数类型。

  1. impl_typeof:生成一个对于特定类型的引用,举个例子:
string str = "hello world!";
impl_typeof( i, str)
-> typeof_str & str

我们可以看到生成了一个引用变量,只不过还没有赋值。

  1. impl_typeof_cpy:都是申请一个变量。和引用差不多,只不过生成的是拷贝而已。
  2. con_param_typeof:生成函数的参数。
  3. 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源码分析系列的完整性去学习了这个,一般来说还是看个人兴趣喽。

参考:
https://blog.csdn.net/MakeZero/article/details/80552509?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159264245719724843328820%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=159264245719724843328820&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-6-80552509.first_rank_v2_rank_v25&utm_term=libco%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90

posted @ 2022-07-02 13:17  李兆龙的博客  阅读(52)  评论(0编辑  收藏  举报