博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++中模拟反射填充消息struct
阅读量:5830 次
发布时间:2019-06-18

本文共 6060 字,大约阅读时间需要 20 分钟。

hot3.png

165840_2H7R_1431439.jpg

问题

我正做的一个项目需要在Erlang 节点和C++ 节点之间传输大量的事件,在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是动态类型的,所以不需要定义,这里只是说明:

{greeting, Sender ::string(), Content ::string(), ContentLength ::int(), ContentType ::atom() }{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
(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
();}

问题在于,我们需要为每个消息写这么一大段代码。假如我们的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定义的静态方法里了。

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

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

更好的方案

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

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

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

成员属性

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

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

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

template 
struct auto_field;

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

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

 template 
 struct auto_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 
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 
bool fill(Msg* e);template 
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 
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 
struct fill_impl;#define EMATCH_MAX_FIELD 8 #define BOOST_PP_ITERATION_LIMITS (1,8)#define BOOST_PP_FILENAME_1 
 #include BOOST_PP_ITERATE()};template
struct fill : aux::fill_impl
::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
, auto_field
> 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
::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 trueso.b == falseso.s == hello c++请按任意键继续. . .

至此,我们实现了想要的功能,使用同一份代码(fill)将Erlang tuple直接填充到指定的C++结构中,而不必大量重复填充代码。

欢迎关注我的微信账号,不定期推送博客更新。

105747_XMUg_1431439.jpg

转载于:https://my.oschina.net/u/1431439/blog/196181

你可能感兴趣的文章
《写给大忙人看的java se 8》笔记
查看>>
倒计时:计算时间差
查看>>
Linux/windows P2V VMWare ESXi
查看>>
Windows XP倒计时到底意味着什么?
查看>>
tomcat一步步实现反向代理、负载均衡、内存复制
查看>>
运维工程师在干什么学些什么?【致菜鸟】
查看>>
Linux中iptables详解
查看>>
java中回调函数以及关于包装类的Demo
查看>>
maven异常:missing artifact jdk.tools:jar:1.6
查看>>
终端安全求生指南(五)-——日志管理
查看>>
Nginx 使用 openssl 的自签名证书
查看>>
创业维艰、守成不易
查看>>
PHP环境安装套件:快速安装LAMP环境
查看>>
CSS3
查看>>
ul下的li浮动,如何是ul有li的高度
查看>>
C++ primer plus
查看>>
python mysqlDB
查看>>
UVALive 3942 Remember the Word Tire+DP
查看>>
从微软的DBML文件中我们能学到什么(它告诉了我们什么是微软的重中之重)~目录...
查看>>
被需求搞的一塌糊涂,怎么办?
查看>>