C++中模拟反射填充消息struct

问题
我正做的一个项目需要在Erlang 节点和C++ 节点之间传输大量的事件,在C++这一侧,使用struct储存这些消息。

C++中模拟反射填充消息struct

自然的,我需要很多struct,例如:

struct msg_greeting{ 

std::string sender; 

std::string content; 

int        content_length; 

std::string content_type; 

}; 

struct msg_bye{ 

std::string sender; 

};


在Erlang这一侧,使用tuple储存,由于Erlang是动态类型的,所以不需要定义,这里只是说明:

view sourceprint?1 {greeting, Sender ::string(), Content ::string(), ContentLength ::int(), ContentType ::atom() } 

2 {bye, Sender ::string() }


消息的传输可以使用tinch_pp ()

如果你第一次使用tinch_pp,下面这一段是一个简单的接收和匹配的过程,即使不了解tinch_pp也可以看懂:


void connect::msg_receiver_routine() 

try{ 

while(1) { 

matchable_ptr msg = mbox->receive(); 

int token; 

std::string type; 

matchable_ptr body; 

if(msg->match( 

make_e_tuple(atom("event"), 

e_string(&type)), 

any(&body))) 

//do something here 

else

//some log here 

}catch(boost::thread_interrupted e){ 

// @todo output some log here 

};


我们使用event标识一个erlang事件,type是这个事件的类型,body是事件内容,也就是我们之前定义的greeting或者bye。

接下来,我们需要实现事件的处理,首先,我们需要把tinch_pp匹配出来的tuple填入我们的c++结构。

我们这样做:

msg_ptr on_greeting(matchable_ptr p){ 

std::string sender; 

std::string content; 

int contentLength; 

std::string contentType; 

bool matched = p->match(make_e_tuple( 

erl::string(&sender), 

erl::string(&content), 

erl::int_(&contentLength), 

erl::atom(&contentType) 

)); 

if(matched){ 

msg_ptr = shared_ptr<msg_greeting>(new msg_greeting()); 

msg_ptr->Sender = sender; 

msg_ptr->Content = content; 

msg_ptr->ContentLength = contentLength; 

msg_ptr->ContentType = contentType; 

return msg_ptr; 

return shared_ptr<msg_greeting>(); 

}


问题在于,我们需要为每个消息写这么一大段代码。假如我们的C Node需要处理几十种消息,我们就需要把这个代码重复几十遍,而实际上只有一小部分才是有用的(有差异的)。

提取通用代码

怎样才能省去重复的部分,只保留其中的精华呢?这就需要元编程和预处理器了,我们稍后再介绍。

首先,最显著的差异就是不同的消息中的信息不一样,用c++的说法是:他们具有不同的成员。

去掉这个差异后,我们的代码可以简化为:
msg_ptr on_greeting(matchable_ptr p){     

if(matched){ 

msg_ptr mp = msg_greeting::make(p); 

return mp; 

return shared_ptr<msg_greeting>(); 

}


看似简洁了许多,但实际上,我们只是把msg_greeting特有的处理(差异)隐藏在msg_greeting定义的静态方法里了。

至少,我们的on_xxxx方法看起来干净点了。

但是,我们还是需要在某处(msg_greeting内部)定义这些代码。

更好的方案

反射是很多语言都具有的特性,反射意味着类具有了自省的能力,即一个类知道自己有哪些成员,这些成员有哪些属性。


如果C++支持反射,我们这个问题就好解决了,我们可以定义一个msg_fill方法,按照msg成员的属性,从matchable_ptr获取成员的值。等等,C++可没有反射支持,至少我不知道。

那么,我们自己来实现一个吧。

成员属性

我们需要一个能保存成员属性的,符合C++语法的物件。有两种选择:对象,类型。


对象对应着运行时,类型对应着编译时。考虑到速度和效率,我们选择类型。

在C++进行元编程,主要是依靠模板来实现的,首先我们声明一个模板,用来表示成员

template <class Type ,class Struct, Type(Struct::*Field)> 

struct auto_field;


这个模板有三个参数:Type表示成员的C++类型,Struct表示这个成员所属的结构,Field是成员指针,用来记住这个成员在所属结构中所处的位置。


光有声明没有什么作用,所以我们需要一些实现(或者说模板定义):

template <class Struct, bool(Struct::*Field)> 

struct auto_field<bool, Struct, Field>{ 

typedef tinch_pp::erl::atom field_e_type; 

typedef std::string field_c_type; 

static void fill(Struct* s, field_c_type& c){ 

s->*Field = (c == "true"); 

}; 

};


可以看出,我们通过模板特化,为bool类型的成员提供了:

C++类型

Erlang类型

填充C++类型的fill方法

这里其实隐藏了一个问题,怎么知道需要定义这几个类型和静态成员函数呢?稍后再介绍。


类似的,我们可以为更多的类型提供特化,不再重复。

至此,我们已经知道怎么定义类型成员,并记住成员的属性。

填充数据
有了成员的属性,我们就可以解析消息tuple了,参考最初的代码,填充方法的伪实现应该长这样:

template <class Msg> 

bool fill(Msg* e){ 

field_0_c_type field_0_c; 

field_1_c_type field_1_c;     

field_2_c_type field_2_c;     

bool matched = p->match(make_e_tuple( 

field_0_e_type(&field_0_c), 

field_1_e_type(&field_1_c), 

field_2_e_type(&field_2_c) 

)); 

if(matched){ 

Event::fill(e,field_0_c); 

Event::fill(e,field_1_c); 

Event::fill(e,field_2_c); 

return true; 

return false; 

};


到此,我们发现不同事件的成员数目是不同的,所以,上述伪代码只能适应成员数为3的消息。

那么,我们就需要提供一组fill实现,每个负责一个成员数。同样,使用模板参数和模板特化来实现:

template <int Size,class Msg> 

bool fill(Msg* e); 

template <class Msg> 

bool fill<1,Msg>(Msg* e){ 

field_0_c_type field_0_c; 

bool matched = p->match(make_e_tuple( 

field_0_e_type(&field_0_c) 

)); 

if(matched){ 

Event::fill(e,field_0_c); 

return true; 

return false; 

}; 

template <class Msg> 

bool fill<2,Msg>(Msg* e) 

field_0_c_type field_0_c; 

field_1_c_type field_1_c; 

......


额~ 这不是又重复了吗?

别急,我们可以用boost::preprocess收敛这些实现,boost::preprocess用来生成重复的代码,

使用后,我们的fill方法长这样:

namespace aux { 

template <int FieldListSize,typename FieldList> 

struct fill_impl; 

#define EMATCH_MAX_FIELD 8 

#define BOOST_PP_ITERATION_LIMITS (1,8) 

#define BOOST_PP_FILENAME_1 <e_match_impl.h> 

#include BOOST_PP_ITERATE() 

}; 

template<typename FieldList> 

struct fill : aux::fill_impl<boost::mpl::size<FieldList>::type::value , FieldList>{ 

};


怎么回事?fill方法消失了?

不,并没有消失,我们把他隐藏在

e_match_impl.h

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/3a27760c9affe1dd2660f90f2d3e0020.html