簡體   English   中英

如何設計帶有“注釋”字段的類?

[英]How to design a class with “annotated” fields?

想象一下,我們有一些具有數百種消息類型的協議,每種消息類型都要由C ++類建模。 由於每個類應該能夠自動處理每個字段,因此一個自然的解決方案就是擁有一個包含所有必需類型的std::tuple

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

print(message);   // the usual variadic magic

這一切都很好。 但是,現在我想給每個字段命名,我希望能夠在引用代碼中的字段時使用該名稱,並獲得它的文本表示。 天真地,或者在C中,我可能寫過:

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

這樣我們就失去了元組的遞歸自動處理能力,但我們可以從字面上命名每個字段。 在C ++中,我們可以通過枚舉來做到這兩點:

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; Message m; m.get<Message::header>() = 12; 我可以在字段上進行遞歸,並使每個打印出自己的值,並以自己的名字等為前綴。


現在的問題是: 如何有效地編寫這樣的代碼而不重復?

理想情況下,我希望能夠這樣說:

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

有沒有辦法,結合預處理器,Boost和C ++ 11,實現這樣的東西,而不需要外部生成工具? (我認為Boost.Preprocessor稱之為“水平”和“垂直”重復。我需要以某種方式“轉置”字段數據。)這里的關鍵特征是我永遠不必重復任何信息,並且修改或添加一個字段只需要一次更改。

您可以使用boost的預處理器序列來完成此操作。

#define CREATE_MESSAGE(NAME, SEQ) ...

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

您需要迭代每對以生成定義。 我沒有任何示例代碼方便,但如果有趣的話我可以安排一些。

有一次,我有一個這樣的東西的生成器,也生成了字段的所有序列化。 我覺得它有點太過分了。 我覺得這些領域的具體定義和聲明訪問者更直接。 如果其他人不得不在我之后維護代碼,那就不那么神奇了。 我顯然不清楚你的情況,在實施之后我仍然有所保留。 :)

用C ++ 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;
}

get的decltype存在問題。 我還沒有真正用元組知道那里有什么期待。 但是,我不認為它與您如何生成類型或字段有任何關系。

以下是預處理器使用-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]; 
  } 
};

這不是一個答案,而只是另一個(可怕的)想法。 我有一個我寫過的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創建具有命名成員的自定義位域類型。 然后可以輕松使用,包括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;

內聯文件本身就是可怕的代碼,但是一些模糊的方法可能會簡化調用者的使用。

如果我以后有時間,我會看看我是否可以根據你想要的東西制作一些東西。

根據Tom Kerr的建議,我查找了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; };

您可以執行類似於BOOST_SERIALIZATION_NVP (來自Boost.Serialization庫)的操作。 宏創建了一個(短命的)包裝器結構,它將其參數的名稱和值綁定在一起。 然后,這個名稱 - 值對由庫代碼處理(名稱實際上僅在XML序列化中很重要,否則將被丟棄)。

所以,你的代碼看起來像:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM