0
点赞
收藏
分享

微信扫一扫

libco源码解析(9) closure实现

芝婵 2022-07-13 阅读 69


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实现

文章目录

  • ​​引言​​
  • ​​基类​​
  • ​​1.1 comac_argc​​
  • ​​1.2 repeat​​
  • ​​2. implement​​
  • ​​2.1 reference​​
  • ​​2.2 function​​
  • ​​总结​​

引言

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
#define
#define
#define

#define
#define

这些函数就是为了完成一个功能,即求出传入的可变参数的个数,当然从源码中我们也可以看出来最多支持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
#define
#define
#define
#define
#define
#define

#define

这一部分的功能是对闭包传入的参数的类型声明,根据不同的参数数量调用不同的函数,其实是一个递归的过程。

2. implement

#if
#define
#else
#define
#endif
#define
#define
#define
#define

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

  1. ​decl_typeof​​:使用typeod_a来代表传入参数的类型,举个例子:

string str = "hello world!";
decl_typeof( i, str)
-> typedef decltype( str )

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

  1. ​impl_typeof​​:生成一个对于特定类型的引用,举个例子:

string str = "hello world!";
impl_typeof( i, str)
-> typeof_str &

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

  1. ​impl_typeof_cpy​​:都是申请一个变量。和引用差不多,只不过生成的是拷贝而已。
  2. ​con_param_typeof​​:生成函数的参数。
  3. ​param_init_typeof​​:生成函数初始化列表。

2.1 reference

#define

这里的​​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

#define

我们像上面一样,举一个简单的例子:

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源码分析系列的完整性去学习了这个,一般来说还是看个人兴趣喽。



举报

相关推荐

0 条评论