2012-03-27 17 views
9

कल्पना कीजिए कि हमारे पास सैकड़ों संदेश प्रकारों के साथ कुछ प्रकार का प्रोटोकॉल है, जिनमें से प्रत्येक हम एक सी ++ वर्ग द्वारा मॉडल करना चाहते हैं। के बाद से प्रत्येक वर्ग के स्वचालित रूप से प्रत्येक क्षेत्र पर कार्रवाई करने के लिए सक्षम होना चाहिए, एक प्राकृतिक समाधान सिर्फ सभी आवश्यक प्रकार के साथ एक std::tuple है:"एनोटेटेड" फ़ील्ड वाले क्लास को कैसे डिज़ाइन किया जाए?

std::tuple<int, double, char> message; 

print(message); // the usual variadic magic 

यह सब ठीक है और अच्छी तरह से है। हालांकि, अब मैं प्रत्येक फ़ील्ड को एक नाम देना चाहता हूं, और मैं अपने कोड में फ़ील्ड का जिक्र करते समय नाम का उपयोग करने में सक्षम होना चाहता हूं, साथ ही इसके पाठ का प्रतिनिधित्व भी प्राप्त करना चाहता हूं। भोलेपन से, या सी में, मैं लिखा है हो सकता है:

struct Message 
{ 
    int header; 
    double temperature; 
    char flag; 
}; 

इस तरह हम टपल की पुनरावर्ती automagic प्रसंस्करण शक्ति खो देते हैं, लेकिन हम प्रत्येक क्षेत्र सचमुच नाम कर सकते हैं। सी ++ में, हम एक enum के माध्यम से दोनों कर सकते हैं:

struct Message 
{ 
    enum FieldID { header, temperature, flag }; 
    static const char * FieldNames[] = { "header", "temperature", "flag" }; 

    typedef std::tuple<int, double, char> tuple_type; 

    template <FieldID I> 
    typename std::tuple_element<I, tuple_type>::type & get() 
    { return std::get<I>(data); } 

    template <FieldID I> 
    static const char * name() { return FieldNames[I]; } 

    tuple_type data; 
}; 

अब मैं कह सकता हूँ, Message m; m.get<Message::header>() = 12; आदि, और मैं क्षेत्रों पर recurse और प्रत्येक प्रिंट आउट अपने स्वयं के मूल्य अपने खुद के नाम से उपसर्ग कर सकते हैं, आदि


अब सवाल: मैं ऐसे कोड कैसे लेखक कर सकते हैं कुशलतापूर्वक, बिना दोहराव के?

START_MESSAGE(Message) 
ADDFIELD(int, header) 
ADDFIELD(double, temperature) 
ADDFIELD(char, flag) 
END_MESSAGE 

वहाँ कोई तरीका है, बाहरी पीढ़ी उपकरण की आवश्यकता के बिना पूर्वप्रक्रमक, बूस्ट और सी ++ 11, कुछ इस तरह प्राप्त करने के लिए संयोजन:

आदर्श रूप में, मैं यह कहना सक्षम होना चाहते हैं ? (मुझे लगता है कि बूस्ट.प्रप्रोसेसर इस "क्षैतिज" और "लंबवत" पुनरावृत्ति को बुलाता है। मुझे किसी भी तरह से डेटा डेटा को "ट्रांसफर" करने की आवश्यकता है।) यहां मुख्य विशेषता यह है कि मुझे कभी भी किसी भी जानकारी को दोहराना नहीं पड़ता है, और यह संशोधित या जोड़ना एक फ़ील्ड को केवल एक ही बदलाव की आवश्यकता होती है।

+1

समस्याओं के इस प्रकार, एक साधारण वर्णनात्मक भाषा और एक कस्टम पूर्वप्रक्रमक एक फ़ाइल को शामिल पैदा करने के लिए यहाँ है कि मैं क्या के साथ आया है लंबे समय तक सबसे अच्छा है। "स्रोत फ़ाइल" आसानी से रखरखाव योग्य है, और बाहरी उपकरणों द्वारा भी उत्पन्न किया जा सकता है, परियोजना को इसकी आवश्यकता होनी चाहिए। –

+0

@AlexandreC .: किसी दिए गए चरण में, "केवल एक और छोटा टूल" हमेशा अच्छे जवाब की तरह दिखता है। लेकिन चीजों की भव्य योजना में, आपके पास बस ले जाने और बनाए रखने और दस्तावेजों को याद रखने और उन्हें प्रशिक्षित करने के लिए एक और चीज है। बॉक्स से बाहर काम करने वाली कुछ चीज निश्चित रूप से कुछ भयानक मैक्रोज़ स्थापित करने के दर्द के लायक है। –

उत्तर

3

आप बूस्ट के प्रीप्रोसेसर अनुक्रमों के साथ ऐसा कर सकते हैं।

#define CREATE_MESSAGE(NAME, SEQ) ... 

CREATE_MESSAGE(SomeMessage, 
    (int)(header) 
    (double)(temperature) 
    (char)(flag) 
) 

आपको परिभाषाओं को उत्पन्न करने के लिए प्रत्येक जोड़ी पर फिर से प्रयास करने की आवश्यकता होगी। मेरे पास कोई उदाहरण कोड आसान नहीं है, हालांकि अगर यह दिलचस्प है तो मैं शायद कुछ व्यवस्थित कर सकता हूं।

एक बिंदु पर मेरे पास ऐसा कुछ जनरेटर था जिसने खेतों के लिए सभी क्रमिकरण भी उत्पन्न किए। मुझे लगता है जैसे यह थोड़ा बहुत दूर चला गया। मुझे लगता है कि खेतों में ठोस परिभाषाएं और घोषणात्मक आगंतुक अधिक सीधे आगे हैं। अगर किसी और को मेरे बाद कोड बनाए रखना पड़ा तो यह थोड़ा कम जादुई है। मुझे नहीं पता कि आप स्पष्ट रूप से स्थिति हैं, इसे लागू करने के बाद भी मुझे आरक्षण था। :)

सी ++ 11 सुविधाओं के साथ फिर से देखना अच्छा होगा, हालांकि मुझे कोई मौका नहीं मिला है।

अद्यतन:

अभी भी बाहर काम करने में कुछ अड़चनों में हैं, लेकिन यह ज्यादातर काम कर रहा है।

#include <boost/preprocessor.hpp> 
#include <boost/preprocessor/seq/for_each_i.hpp> 
#include <boost/preprocessor/arithmetic/mod.hpp> 
#include <boost/preprocessor/control/if.hpp> 

#include <tuple> 

#define PRIV_CR_FIELDS(r, data, i, elem) \ 
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),elem BOOST_PP_COMMA,BOOST_PP_EMPTY)() 

#define PRIV_CR_STRINGS(r, data, i, elem) \ 
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_STRINGIZE(elem) BOOST_PP_COMMA,BOOST_P 

#define PRIV_CR_TYPES(r, data, i, elem) \ 
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_EMPTY,elem BOOST_PP_COMMA)() 

#define CREATE_MESSAGE(NAME, SEQ) \ 
    struct NAME { \ 
     enum FieldID { \ 
      BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_FIELDS, _, SEQ) \ 
     }; \ 
     std::tuple< \ 
      BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_TYPES, _, SEQ) \ 
     > data;\ 
     template <FieldID I> \ 
      auto get() -> decltype(std::get<I>(data)) { \ 
       return std::get<I>(data); \ 
      } \ 
     template <FieldID I> \ 
      static const char * name() { \ 
       static constexpr char *FieldNames[] = { \ 
        BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_STRINGS, _, SEQ) \ 
       }; \ 
       return FieldNames[I]; \ 
      } \ 
    }; 

CREATE_MESSAGE(foo, 
     (int)(a) 
     (float)(b) 
    ) 

#undef CREATE_MESSAGE 

int main(int argc, char ** argv) { 

    foo f; 
    f.get<foo::a>() = 12; 

    return 0; 
} 

इसमें गिरावट के साथ समस्याएं आ रही हैं। मैंने वास्तव में यह जानने के लिए tuple का उपयोग नहीं किया है कि वहां क्या उम्मीद करनी है। मुझे नहीं लगता कि इसके साथ आप कुछ प्रकार या फ़ील्ड कैसे उत्पन्न करते हैं, इसके साथ कुछ भी करना है।

यहाँ पूर्वप्रक्रमक -E साथ उत्पादन किया जाता है:

struct foo { 
    enum FieldID { a , b , }; 
    std::tuple< int , float , > data; 
    template <FieldID I> 
    auto get() -> decltype(std::get<I>(data)) { 
     return std::get<I>(data); 
    } 
    template <FieldID I> static const char * name() { 
    static constexpr char *FieldNames[] = { "a" , "b" , }; 
    return FieldNames[I]; 
    } 
}; 
+0

जो वादा करता है। मुझे इसके दस्तावेज को पढ़ने दो। यदि आपके पास एक पूर्ण कोड उदाहरण है, तो मैं बहुत आभारी हूं (और शायद कुछ बक्षीस भेजूंगा)। –

+0

@KerrekSB यहां मजबूत टाइप किए गए enums बनाने की कोशिश करने वाले किसी व्यक्ति के लिए एक लिंक (बूस्ट वॉल्ट) का संदर्भ दिया गया है, जो दिखाएगा कि आप कम से कम अनुक्रम पर पुनरावृत्ति करेंगे। http://stackoverflow.com/a/439004/839436 –

+0

@KerrekSB अपडेट किया गया। यह अब तक पूरी तरह से काम नहीं कर रहा है, लेकिन ऐसा लगता है कि संरचना क्षेत्र उत्पन्न किए जा रहे हैं। –

1

यह कोई जवाब नहीं है, लेकिन केवल एक और (डरावना) विचार है। मेरे पास inl फ़ाइल है जिसे मैंने एक बार लिखा था कि थोड़े प्रकार की तरह ही समान रूप से समान है। यह यहाँ है: http://ideone.com/6CvgR

मूल अवधारणा फोन करने वाले यह करता है:

#define BITNAME color 
#define BITTYPES SEPERATOR(Red) SEPERATOR(Green) SEPERATOR(Blue) 
#define BITTYPE unsigned char 
#include "BitField.inl" 

और inl फ़ाइल SEPERATOR को पुनर्परिभाषित करने और उसके बाद फिर BITTYPES का उपयोग करके नामित सदस्यों के साथ एक कस्टम bitfield प्रकार पैदा करता है। जिसे ToString फ़ंक्शन समेत आसानी से उपयोग किया जा सकता है।

colorBitfield Pixel; 
Pixel.BitField = 0; // sets all values to zero; 
Pixel.Green = 1; // activates green; 
std::cout << "Pixel.Bitfield=" << (int)Pixel.BitField << std::endl; //this is machine dependant, probably 2 (010). 
Pixel.BitField |= (colorBitfield::GreenFlag | colorBitfield::BlueFlag); // enables Green and Blue 
std::cout << "BlueFlag=" << (Pixel.BitField & colorBitfield::BlueFlag) << std::endl; // 1, true. 
std::cout << "sizeof(colorBitField)=" << sizeof(colorBitfield) << std::endl; 

इनलाइन फ़ाइल स्वयं कोड भयानक है, लेकिन थोड़ा इस तरह की कुछ दृष्टिकोण फोन करने वाले का उपयोग को आसान बनाने में कर सकते हैं।

यदि मेरे पास समय है, तो मैं देखूंगा कि मैं इस विचार के साथ कुछ कर सकता हूं जो आप चाहते हैं।

0

आप क्या BOOST_SERIALIZATION_NVP (Boost.Serialization पुस्तकालय से) करता है के लिए इसी तरह कुछ कर सकते हैं। मैक्रो एक (अल्पकालिक) रैपर संरचना बनाता है जो इसके तर्क और मूल्य के नाम को एक साथ बांधता है। यह नाम-मूल्य जोड़ी तब लाइब्रेरी कोड द्वारा संसाधित की जाती है (नाम वास्तव में एक्सएमएल क्रमिकरण में केवल महत्वपूर्ण है, अन्यथा इसे छोड़ दिया जाता है)।

int header  = 42; 
double temperature = 36.6; 
char flag  = '+'; 
print (Message() + MY_NVP (header) + MY_NVP (temperature) + MY_NVP (flag)); 
+0

एचएम, रोचक ... मैं सोच रहा था कि बूस्ट।serialization में कोई उपकरण हो सकता है जो नौकरी के साथ मदद कर सकता है। लेकिन मैं वास्तव में वास्तविक 'संदेश' वर्ग को चारों ओर पसंद करूंगा। मेरे पास पहले से ही सीरियलाइजेशन कोड है, इसलिए मैं सिर्फ एक को चालू करना चाहता हूं, इसे पॉप्युलेट कर सकता हूं और इसे अपने सीरिएलाइज़र को भेज सकता हूं, उदाहरण के लिए, या अपनी सामग्री को लॉग फ़ाइल में प्रिंट कर सकता हूं। –

+0

@ केरेकस्क: मुझे लगता है कि मैंने आपको गलत समझा। क्या आप 'संदेश' वर्ग रखना चाहते हैं क्योंकि यह अब * है * और * समान वर्ग बनाने के लिए भी आसानी से संभव बनाता है ('message1', ...' message53')? – doublep

+0

हां, वास्तव में। मैं कई संदेश वर्ग (स्थायी रूप से) डिज़ाइन करना चाहता हूं, और मैं उन सभी वर्गों * लेखक * के लिए एक आसान तरीका चाहता हूं। जैसा कि मैंने उदाहरण में किया था, मैं बस मैन्युअल रूप से लिख सकता हूं, लेकिन यह कठिन और भयानक होगा। मैं कक्षा परिभाषाओं को बनाने के लिए एक बाहरी उपकरण भी लिख सकता हूं, लेकिन वह भी असंतोषजनक होगा। –

1

टॉम केर के सुझाव के आधार पर, मैं Boost.Preprocessor दृश्यों को देखा:

तो, अपने कोड की तरह लग सकता है।

#include <boost/preprocessor/seq.hpp> 
#include <boost/preprocessor/comma_if.hpp> 
#include <boost/preprocessor/arithmetic.hpp> 
#include <boost/preprocessor/stringize.hpp> 

#include <tuple> 

#define PROJECT1(a,b) a 
#define PROJECT2(a,b) b 

#define BOOST_TT_projectqu(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) BOOST_PP_STRINGIZE(PROJECT2 t) 
#define BOOST_TT_project1(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) PROJECT1 t 
#define BOOST_TT_project2(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) PROJECT2 t 


template <typename T> struct Field { }; 

#define MESSAGE(classname, data) struct classname            \ 
    {                        \ 
     typedef std::tuple<BOOST_PP_SEQ_FOR_EACH(BOOST_TT_project1, ~, data)> tuple_type;   \ 
                           \ 
     static constexpr char const * FieldNames[BOOST_PP_SEQ_SIZE(data)] = { BOOST_PP_SEQ_FOR_EACH(BOOST_TT_projectqu, ~, data) }; \ 
                           \ 
     enum FieldID { BOOST_PP_SEQ_FOR_EACH(BOOST_TT_project2, ~, data) };      \ 
                           \ 
     template <FieldID I> using type = typename std::tuple_element<I, tuple_type>::type;  \ 
                           \ 
     template <FieldID I> typename std::tuple_element<I, tuple_type>::type & get() { return std::get<I>(dat); } \ 
     template <FieldID I> typename std::tuple_element<I, tuple_type>::type const & get() const { return std::get<I>(dat); } \ 
                           \ 
    private:                      \ 
     tuple_type dat;                   \ 
    }; 

MESSAGE(message,   \ 
    ((int, header))   \ 
    ((double,temperature)) \ 
    ((char, flag))   \ 
) 

gcc -std=c++11 -E -P के साथ पूरे बात संकलन (और पुन: फ़ॉर्मेट) देता है::

template <typename T> struct Field { }; 

struct message { 
    typedef std::tuple< int , double , char > tuple_type; 
    static constexpr char const * FieldNames[3] = { "header" , "temperature" , "flag" }; 
    enum FieldID { header , temperature , flag }; 
    template <FieldID I> using type = typename std::tuple_element<I, tuple_type>::type; 
    template <FieldID I> typename std::tuple_element<I, tuple_type>::type & get() { return std::get<I>(dat); } 
    template <FieldID I> typename std::tuple_element<I, tuple_type>::type const & get() const { return std::get<I>(dat); } 
    private: tuple_type dat; }; 
संबंधित मुद्दे

 संबंधित मुद्दे