Любой способ генерировать повторяющиеся операторы << определения? - PullRequest
3 голосов
/ 20 января 2011

У меня есть несколько десятков таких типов структур, и я надеюсь, что есть умный способ генерации методов operator << с помощью макросов или шаблонного метапрограммирования. Обратите также внимание на то, что endianess также учитывается и делает его более сложным ... Ищите решение, которое было бы по меньшей мере столь же эффективным, как и намеченное. </p>

#define SEP '|'

struct MyStruct {
   char c;
   char s[10];
   uint32_t i;
   unsigned short us;

   friend ostream& operator<<(ostream& os, HeartbeatMessage& r) {
      return os \
         << "c=" << c << SEP
         << "s=" << s << SEP
         << "i=" << bswap_32(i) << SEP
         << "us=" << bswap_16(us) << SEP
}

Ответы [ 4 ]

1 голос
/ 20 января 2011

как только у меня возникла похожая проблема (много разных сетевых пакетов для собственного протокола), я использовал для этого Boost Preprocessor, результат выглядел так:

//////////////////////////////////////////////////////////////////////////
DEF_PACKET_STRUCT(name, members)

Example:

DEF_PACKET_STRUCT(
    Test_Struct,
    ((float) (f) (0.4f))
    ((std::string) (str))
);

defines Test_Struct with 2 members:
float f;
std::string str;

generates def ctor:
Test_Struct(): f(0.4f) {}

generates serialization and operator<<(std::ostream&...)

//////////////////////////////////////////////////////////////////////////
DEF_DERIVED_PACKET_STRUCT(name, bases, members)
the same as above + derivation from given bases

Example: 
DEF_DERIVED_PACKET_STRUCT(Test_Struct, 
    (Base1)(Base2), 
    ((std::string) (str_multi_derived) ("multi_derived"))
)

Note that even if it should be derived from single base, it should be specified in (), e.g. (Base1)

функция endianess может быть реализована так же, как и значение по умолчанию для члена в этом примере

1 голос
/ 20 января 2011

Как отмечено в комментариях, это было бы хорошо с отражением. Поскольку в C ++ нет встроенного отражения, лучшее, что вы можете сделать, - это использовать библиотеку или код домашнего пивоварения для имитации отражения и реализации вашей печати в общем виде.

Я предлагаю вам использовать Boost.Fusion, в частности BOOST_FUSION_ADAPT_STRUCT [ Link ], чтобы сделать ваш тип работоспособным в качестве последовательности слияния, а затем использовать Fusion for_each для определения печати.

Увеличенная работа, необходимая для того, чтобы в основном повторить определение структуры для эмуляции отражения, быстро окупится, если у вас есть несколько методов, работающих с элементами. Однако, если все, что у вас есть на самом деле, это один operator<<, то, вероятно, оно того не стоит (или вы решите, по крайней мере, вы можете определить всю свою печать и форматирование в одном месте).

0 голосов
/ 20 января 2011

Есть альтернатива, но она становится ужасной.
Одним из решений является создание базового базового класса, такого как Object (на других языках).
Следующим шагом является создание контейнеров указателей на Object.
И, наконец, в какой-то момент напишите метод, который применяет operator<< к каждому объекту в контейнере (через указатели).

В противном случае, я иду с чем-то вроде этого:

struct Annotation_Interface
{
    virtual std::string annotate(const std::string& indentation = "") = 0;
};

class MyStruct : Annotation_Interface
{
   char c;
   char s[10];
   uint32_t i;
   unsigned short us;

  public:
    std::string annotate(const std::string& indentation)
    {
        std::ostringstream output;
        output << "c=" << c << SEP
         << "s=" << s << SEP
         << "i=" << bswap_32(i) << SEP
         << "us=" << bswap_16(us) << SEP;
        return output.str();
     }
};
0 голосов
/ 20 января 2011

Предположительно имена и типы членов различаются в ваших структурах, в противном случае нет проблем.

И, вероятно, вам нужны ваши структуры, чтобы быть POD?

В этом случае разные структуры POD, главное ограничение в том, что шаблоны не могут обрабатывать обычные идентификаторы C ++ (что вы обязательно должны иметь для членов в структуре POD). Поэтому вам нужен макрос для определения operator<<; только макросы могут обрабатывать идентификаторы. И тогда основная проблема заключается в том, как передать переменное число аргументов в макрос?

Ну, есть способы передать переменное число аргументов и перебрать их, используя поддержку макросов библиотеки Boost, но это сложно.

В любом случае вам придется указывать имена членов данных, поэтому будет не более чистым, чем код, который у вас уже есть .

Имея это в виду и воздерживаясь от соблазна использовать Boosted variadic macro, это может выглядеть так (я бы не стал использовать это, но если вам это нравится, вы можете также рассмотреть определение макроса для объявления заголовка функции) :

#include <iostream>
#include <string>

#define bswap_32( x )   x
#define bswap_16( x )   x
typedef unsigned    uint32_t;

char const  sep     = '|';

template< class Type >
inline void write( Type const& v, std::ostream& stream )
{
    stream << v;
}

template<>
inline void write( uint32_t const& v, std::ostream& stream )
{
    stream << bswap_32( v );
}

template<>
inline void write( unsigned short const& v, std::ostream& stream )
{
    stream << bswap_16( v );
}

template< class Type >
inline void write( char const legend[], Type const& v, std::ostream& stream )
{
    stream << legend;  write( v, stream );  stream << '|';
}

#define IMPLEMENT_OUTPUT_1(                     \
    name1                                       \
    )                                           \
    write( #name1 "=", r.name1, os );

#define IMPLEMENT_OUTPUT_2(                     \
    name1, name2                                \
    )                                           \
    IMPLEMENT_OUTPUT_1( name1 )                 \
    write( #name2 "=", r.name2, os );

#define IMPLEMENT_OUTPUT_3(                     \
    name1, name2, name3                         \
    )                                           \
    IMPLEMENT_OUTPUT_2( name1, name2 )          \
    write( #name3 "=", r.name3, os );

#define IMPLEMENT_OUTPUT_4(                     \
    name1, name2, name3, name4                  \
    )                                           \
    IMPLEMENT_OUTPUT_3( name1, name2, name3 )   \
    write( #name4 "=", r.name4, os );


struct MyStruct
{
   char c;
   char s[10];
   uint32_t i;
   unsigned short us;

//    friend std::ostream& operator<<( std::ostream& os, MyStruct const& r )
//    {
//       return os
//          << "c=" << r.c << sep
//          << "s=" << r.s << sep
//          << "i=" << bswap_32( r.i ) << sep
//          << "us=" << bswap_16( r.us ) << sep;
//     }

    friend std::ostream& operator<<( std::ostream& os, MyStruct const& r )
    {
        IMPLEMENT_OUTPUT_4( c, s, i, us )
        return os;
    }
};

int main()
{
    using namespace std;
    MyStruct const  o   = { 'A', "Bbbbbbb", 3, 4 };

    cout << o << endl;
}

Опять же, я бы не стал использовать такую ​​схему. Но, за исключением Boost'ing, он ближе всего подходит для различных структур POD. Так что, возможно, стоило бы увидеть это.

Привет и извините, что это, вероятно, не помогает,

...