歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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

問題
我正做的一個項目需要在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