我正作的一個項目須要在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++結構中,而沒必要大量重複填充代碼。
歡迎關注個人微信帳號,不按期推送博客更新。