歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C++中模擬反射填充消息struct

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

日期:2017/3/1 9:48:57   编辑:Linux編程

問題
我正做的一個項目需要在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是動態類型的,所以不需要定義,這裡只是說明:

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

2 {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。

接下來,我們需要實現事件的處理,首先,我們需要把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


這個文件裡,通過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("[email protected]");

const std::string to_name("reflect_msg");

tinch_pp::node_ptr my_node = tinch_pp::node::create("[email protected]", "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,運行上述代碼前,需要先把他啟動起來:

view sourceprint?1 werl -pa . -sname testnode -setcookie abcdef


運行後,應該打印出:

ret is true

so.b == false

so.s == hello c++

請按任意鍵繼續. . .


至此,我們實現了想要的功能,使用同一份代碼(fill)將Erlang tuple直接填充到指定的C++結構中,而不必大量重復填充代碼。

Copyright © Linux教程網 All Rights Reserved