我正作的一个项目须要在Erlang 节点和C++ 节点之间传输大量的事件,在C++这一侧,使用struct储存这些消息。node
天然的,我须要不少struct,例如:c++
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是动态类型的,因此不须要定义,这里只是说明:shell
{greeting, Sender ::string(), Content ::string(), ContentLength ::int(), ContentType ::atom() } {bye, Sender ::string() }
消息的传输可使用tinch_pp (http://www.adampetersen.se/code/tinchpp.htm) 编程
若是你第一次使用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。cookie
接下来,咱们须要实现事件的处理,首先,咱们须要把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须要处理几十种消息,咱们就须要把这个代码重复几十遍,而实际上只有一小部分才是有用的(有差别的)。atom
怎样才能省去重复的部分,只保留其中的精华呢?这就须要元编程和预处理器了,咱们稍后再介绍。spa
首先,最显著的差别就是不一样的消息中的信息不同,用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
这个文件里,经过boost::preprocess重复include这个文件8次,从而得到1个到8个成员的fill实现。并经过集成把这个实现模板提供的功能暴露出来,同时收敛其模板参数。
至此,咱们获得了一个能够根据FieldList(成员属性的mpl::list),自动match和填充C++结构的fill方法。
好了,咱们来写一段代码,测试一下上述实现吧:
struct SimpleObject{ bool b; std::string s; }; typedef boost::mpl::list< auto_field<bool, SimpleObject, &SimpleObject::b>, auto_field<std::string, SimpleObject, &SimpleObject::s> > SimpleObjectFields; int _tmain(int argc, _TCHAR* argv[]) { SimpleObject so; const std::string remote_node_name("testnode@127.0.0.1"); const std::string to_name("reflect_msg"); tinch_pp::node_ptr my_node = tinch_pp::node::create("my_test_node@127.0.0.1", "abcdef"); tinch_pp::mailbox_ptr mbox = my_node->create_mailbox(); mbox->send(to_name, remote_node_name, tinch_pp::erl::make_e_tuple(tinch_pp::erl::atom("echo"), tinch_pp::erl::pid(mbox->self()), tinch_pp::erl::make_e_tuple( tinch_pp::erl::make_atom("false"), tinch_pp::erl::make_string("hello c++") ))); const tinch_pp::matchable_ptr reply = mbox->receive(); bool ret = fill<SimpleObjectFields>::fill_on_match(&so,reply); printf("ret is %s \n",(ret?"true":"false")); printf("so.b == %s \n",(so.b?"true":"false")); printf("so.s == %s \n",so.s.c_str()); system("pause"); return 0; }
因为我没有找到tinch_pp怎么构造一个matchable_ptr,因此须要一个erlang的外部节点把我构造的tuple反射回来,tinch_pp已经提供了这样的一个server,运行上述代码前,须要先把他启动起来:
werl -pa . -sname testnode -setcookie abcdef
运行后,应该打印出:
ret is true so.b == false so.s == hello c++ 请按任意键继续. . .
至此,咱们实现了想要的功能,使用同一份代码(fill)将Erlang tuple直接填充到指定的C++结构中,而没必要大量重复填充代码。
欢迎关注个人微信帐号,不按期推送博客更新。