Fantastic
C++14有很多新特性,但是最让人着迷的,莫过于变参模板了,变参模板把函数式的编程思想在C++中发挥得淋漓尽致,让C++的灵活性提升了一个level,使用变参模板,可以写出看起来像是动态语言特性的代码.
最近遇到一个问题,如果没有变参模板,还真不知道该如何解决.
Question
我们的后台程序使用了Tars框架,他的IDL生成出来的C++代码中,有一个Callback类用来表示异步调用的回调,定义如下:
|
|
一眼看上去似乎也没什么问题,每个请求对应一个虚函数,要处理这个请求的callback,只需要重载对应的虚函数即可.
就处理业务逻辑来说,其实没啥太大问题.
但是,我现在要写的是一个Proxy,主要负责把内部的Tars接口使用开放的HTTP协议对外暴露,所以对每一个接口的业务逻辑都是一样的.我可不想把同一份代码不停的Copy & Paste.
So… 我想到一个办法,变参模板!
Solution
分析上面的回调函数的定义,发现参数列表有如下规律:
|
|
定义这样一个宏,方便用来给每一个RPC生成统一的Callback类
|
|
使用嵌套类是为了区分请求参数类型列表和回包(出参)参数类型列表
Forward类的模板参数是请求参数列表
Callback类的模板参数是回包参数列表
TCONTEXT是callback允许外带的一个上下文,方便发包和收包的逻辑衔接
TF是这个宏生成出来的类的基类,本例中就是上面出现的FakeCallbackBase
m_processer是用来统一处理所有回包的一个处理器,我们把静态的代码转换为动态的代码,靠的就是这个m_processer
下面来看一下Processer的代码
|
|
CallbackProcesserBase 就是Processer的基类,因为我们要实现的是HTTP的转发,所以使用一个派生类来实现以上的接口定义,代码如下:
|
|
最后,通过变参模板,我实现了一个接口只需要两行代码即可进行转发,一行代码是使用DEFINE_FORWARD_CALLBACK 宏生成一个callback的基类, 一行代码是用来表示这个接口的返回值,入参和出参,代码长相如下:
CASE宏定义,简化最终代码
定义一个接口的转发代码
Dispatcher 的主要逻辑是把一个std::vector
后面,我们可以根据.tars文件定义的RPC接口,来动态生成转发代码,从而实现Proxy服务全自动生成.对外暴露接口再也没有任何工作量了!
Notes
- 我觉得写代码的一大乐趣,就是把复杂的问题简单化,复杂的事情只做一次.尽可能的减少对外暴露的复杂度,就本例来说,基本上算是做到了.
- 以上代码使用了变参模板的递归,和tuple递归,这里的思想其实是和erlang这种函数式语言的for循环一样,使用这种看似很奇怪的写法,是因为模板里面没有变量,只有常量.
- Dispatcher中使用了把变参模板暂存为一个tuple,然后再unpacking, 需要使用另一个函数来中转, 这种写法还是很奇怪,很hacking, 不过似乎也没有其他的写法了.参考这里.
- 核心思想是:
- 把各种类型的Callback通过宏自动生成
- 找一个地方汇总所有的Callback的结果然后统一处理