任何方式生成重复运算符<<定义?

发布于 2024-10-13 05:04:14 字数 551 浏览 3 评论 0 原文

我有几十个这样类型的结构,我希望有一种聪明的方法来生成运算符<<使用宏或模板元编程的方法。另请注意,字节序也是考虑因素,并使其变得更加棘手......寻找一种至少与概述的解决方案一样高效的解决方案。

#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
}

I have a few dozen of these types of structs and I'm hoping there is a clever way of generating the operator<< methods using macros or template meta-programming. Please also notice that endianess is also consideration and makes it a bit more tricky... Looking for a solution that would be at least as performant as the one outlined.

#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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

月下客 2024-10-20 05:04:14

正如评论中所指出的,如果有反思的话那就太好了。由于 C++ 没有内置反射,因此您能做的最好的事情就是使用一些库或自制代码来模拟反射并以通用方式实现打印。

我建议您使用Boost.Fusion,特别是BOOST_FUSION_ADAPT_STRUCT [Link] 使您的类型可作为融合序列使用,然后使用 Fusion 的 for_each 定义打印。

如果您有多种对成员进行操作的方法,那么基本上重复结构体的反射模拟定义所需的增加的工作将很快得到回报。但是,如果您真正拥有的只是单个运算符<<,则可能不值得付出努力(或者您决定,至少您可以在一个中心位置定义所有打印和格式)。

As noted in the comments, this would be nice with reflection. Since C++ doesn't have built-in reflection, the best thing you can do is use some library or home brew code to simulate reflection and implement your printing in a generic way.

I suggest you use Boost.Fusion, specifically BOOST_FUSION_ADAPT_STRUCT [Link] to make your type workable as a fusion sequence and then use Fusion's for_each to define the printing.

The increased work required for basically repeating the struct's definition for reflection-emulation will quickly pay off if you have several methods operating on the members. However, if all you really have is that single operator<<, it's probably not worth the effort (or you decide, at least you can define all your printing&formatting in a central place).

执笔绘流年 2024-10-20 05:04:14

有一次我遇到类似的问题(自定义协议有很多不同的网络数据包),为此使用了 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)

你的字节序功能可以按照与本示例中的成员默认值相同的方式实现

once I had similar problem (a lot of different network packets for custom protocol), used Boost Preprocessor for this, result looked like this:

//////////////////////////////////////////////////////////////////////////
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)

you endianess feature can be implemented the same way as member default value in this example

泼猴你往哪里跑 2024-10-20 05:04:14

推测结构中的成员名称和类型不同,否则没有问题。

想必您需要您的结构是 POD 吗?

在这种情况下,对于不同的 POD 结构,主要约束是模板无法处理普通的 C++ 标识符(这是 POD 结构中的成员所必需的)。因此,您需要一个宏来定义operator<<;只有宏可以处理标识符。然后主要问题是,如何将可变数量的参数传递给宏?

好吧,有一些方法可以使用 Boost 库的宏支持来传递可变数量的参数并迭代它们,但这很复杂。

无论如何,您都必须提供数据成员名称,因此它不会比您已有的代码更干净

记住这一点,并避免使用增强可变参数宏的诱惑,它可以看起来像这样(我不会使用它,但如果你喜欢它,你可以考虑为函数头声明定义一个宏):

#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 结构中最接近的。所以我可能值得一看。

干杯&抱歉,这可能没有帮助,

Presumably member names and types are different in your structs, otherwise no problem.

And presumably you need your structs to be POD?

In that case, different POD structs, the main constraint is that templates can't handle ordinary C++ identifiers (which is what you necessarily have for the members in a POD struct). So you then need a macro to define the operator<<; only macros can handle identifiers. And then the main problem is, how to pass a variable number of arguments to the macro?

Well, there are ways to pass variable number of arguments and iterate over them, using the Boost library's macro support, but that's complicated.

You will have to provide the data member names anyway, and so it will be not be more clean than the code that you already have.

Keeping that in mind, and refraining from the temptation to use Boosted variadic macros, it can look like this (I wouldn't use it, but if you like it you may consider also defining a macro for the function head declaration):

#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;
}

Again, I would not use a scheme like this. But, except for Boost'ing, it's the closest you get for different POD structs. So I might be worth having seen it.

Cheers & sorry that this probably not helps,

絕版丫頭 2024-10-20 05:04:14

有一个替代方案,但它变得很难看。
一种解决方案是创建一个通用基类,例如 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();
     }
};

There is an alternative, but it gets ugly.
One solution is to create a generic base class, such as Object (in other languages).
The next step is to create a containers of pointers to Object.
And finally at some point write a method that applies operator<< to each object in the container (via the pointers).

Otherwise, I go with something like this:

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();
     }
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文