将类序列化为一个已定义的对象

时间:2014-05-04 19:23:06

标签: c++ serialization boost

我想设计一个应用程序协议,在其中我定义了一个名为message的类,它具有4字节的固定头大小和可变有效负载部分。要将有效负载分配给消息,我想按以下方式进行:

 message msg;
 IPAddressv4 ipv4("127.0.0.1");
 IPAddressv6 ipv6("::1");
 Algorithm   alg(2);

 msg.version(1);
 msg.ack(1);

 msg << ipv4 & ipv6 & alg;

其中IPAddressv4,IPAddressv6和Algorithm是使用基本数据类型定义的对象。例如,ipv4是uint32,ipv6是2 * unit64,算法是uint8。

这样最终的邮件大小是: 固定标题大小&#34; 4&#34; + 4 + 16 + 1 = 25字节

那么有人能给我一般的方法如何在c ++中实现它,或者在boost库中是否有这样做?

1 个答案:

答案 0 :(得分:1)

所以,我认为你想要一个类似于流但具有二进制格式的类。

这是我对这类(简化)的简单界面:

struct Message
{
    // serialize all of the inserted buffer content to the given stream
    friend std::ostream& operator <<(std::ostream& os, Message const& msg);

    // read all of the given stream into the message buffer for extraction
    friend std::istream& operator >>(std::istream& is, Message const& msg);

    // insert integral data into the buffer, with fixed endianness
    template <typename Int>
        friend Message& operator <<(Message& msg, Int v);

    template <typename Int, size_t N>
        friend Message& operator <<(Message& msg, Int const (&v)[N]);

    // extract integral data from the buffer, with fixed endianness
    template <typename Int>
        friend Message& operator >>(Message& msg, Int& v);

    template <typename Int, size_t N>
        friend Message& operator >>(Message& msg, Int (&v)[N]);

    // default construction
    Message() : buffer { "\xee\x33" }
    {
        buffer.unsetf(std::ios::skipws);
    }

private:
    std::stringstream mutable buffer;
};

我遗漏了处理SFINAE /元编程的神秘细节。构造函数快速而且很脏,并且显示了一种简单的方法来获得固定的消息头(在这种情况下为ee33)。

现在您可以像这样使用它:

std::string serialize_message()
{
    uint8_t  ipv4[4] = { 127, 0, 0, 1 };
    uint64_t ipv6[2] = { 0xdeadbeef00004b1d, 0xcafed00dcafebabe };
    uint8_t  alg     = 2;

    Message msg;
    msg << ipv4 << ipv6 << alg;

    // stringyfy
    std::stringstream ss;
    ss << msg;
    return ss.str();
}

int main()
{
    std::string const rep = serialize_message();
    std::cout << rep << std::flush; // for demo (view in hex)

    // reconstruct
    Message roundtrip;
    std::istringstream(rep) >> roundtrip;

    uint8_t  ipv4[4] = { 0 };
    uint64_t ipv6[2] = { 0 };
    uint8_t  alg     = 0;
    roundtrip >> ipv4 >> ipv6 >> alg;

    assert(rep == boost::lexical_cast<std::string>(roundtrip));
}

请注意,lexical_cast只使用上面列出的流插入运算符。

我已使用Boost Spirit的二进制parsersgenerators实现此目的。

输入类型到解析器/生成器类型的映射在详细命名空间中。

查看 Live On Coliru 。输出:

0000000: ee33 7f00 0001 dead beef 0000 4b1d cafe  .3..........K...
0000010: d00d cafe babe 02                        .......

完整代码

#define LIVE_DANGEROUSLY
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/integer.hpp>
#include <sstream>

namespace detail
{
    using namespace boost::spirit;
    // meta programming helpers
    template <int bits, typename endianness = struct big> struct int_traits;

    template <typename endianness> struct int_traits<8, endianness/*ignored*/> { 
        typedef uint8_t                  type;
        typedef qi::byte_type            parser;
        typedef karma::byte_type         generator;
    };

    template <> struct int_traits<16, struct big> {
        typedef uint16_t                 type;
        typedef qi::big_word_type        parser;
        typedef karma::big_word_type     generator;
    };

    template <> struct int_traits<32, struct big> {
        typedef uint32_t                 type;
        typedef qi::big_dword_type       parser;
        typedef karma::big_dword_type    generator;
    };

    template <> struct int_traits<64, struct big> {
        typedef uint64_t                 type;
        typedef qi::big_qword_type       parser;
        typedef karma::big_qword_type    generator;
    };

    template <> struct int_traits<16, struct little> {
        typedef uint16_t                 type;
        typedef qi::little_word_type     parser;
        typedef karma::little_word_type  generator;
    };

    template <> struct int_traits<32, struct little> {
        typedef uint32_t                 type;
        typedef qi::little_dword_type    parser;
        typedef karma::little_dword_type generator;
    };

    template <> struct int_traits<64, struct little> {
        typedef uint64_t                 type;
        typedef qi::little_qword_type    parser;
        typedef karma::little_qword_type generator;
    };
}

struct Message
{
    friend std::ostream& operator <<(std::ostream& os, Message const& msg) {
        msg.buffer.seekg(0, std::ios_base::beg);
        return os << msg.buffer.rdbuf();
    }

    friend std::istream& operator >>(std::istream& is, Message const& msg) {
        msg.buffer.clear(); // this will overwrite our header
        msg.buffer.str("");
        return is >> msg.buffer.rdbuf();
    }

    template <typename Int>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator <<(Message& msg, Int v)
        {
            msg.buffer.seekp(0, std::ios_base::end);
            msg.append_int(v);
            return msg;
        }

    template <typename Int, size_t N>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator <<(Message& msg, Int const (&v)[N])
        {
            msg.buffer.seekp(0, std::ios_base::end);
            msg.append_int(v);
            return msg;
        }

    template <typename Int>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator >>(Message& msg, Int& v)
        {
            msg.extract_int(v);
            return msg;
        }

    template <typename Int, size_t N>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator >>(Message& msg, Int (&v)[N])
        {
            msg.extract_ints(v);
            return msg;
        }

    Message() : buffer { "\xee\x33" }
    {
        buffer.unsetf(std::ios::skipws);
    }

  private:
    std::stringstream mutable buffer;

    template <typename Int, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void append_int(Int v)
    {
        namespace karma = boost::spirit::karma;
        static const typename detail::int_traits<bits>::generator gen {};
        karma::generate(std::ostreambuf_iterator<char>(buffer), gen, static_cast<typename detail::int_traits<bits>::type>(v));
    }

    template <typename Int, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void extract_int(Int& v)
    {
        namespace qi = boost::spirit::qi;
        static const typename detail::int_traits<bits>::parser p {};
        boost::spirit::istream_iterator f(buffer), l;
        qi::parse(f, l, p, v);
    }

    template <typename Int, size_t N, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void append_int(Int const(&v)[N])
    {
#ifdef LIVE_DANGEROUSLY // optimize, but implementation defined cast
        namespace karma = boost::spirit::karma;
        static const typename detail::int_traits<bits>::generator gen {};
        auto data = reinterpret_cast<typename detail::int_traits<bits>::type const*>(&v[0]);
        karma::generate(std::ostreambuf_iterator<char>(buffer), +gen, boost::make_iterator_range(data,data+N));
#else
        for(auto& i:v)
            append_int(i);
#endif
    }

    template <typename Int, size_t N, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void extract_ints(Int (&v)[N])
    {
        for(auto& i:v)
            extract_int(i);
    }
};

std::string serialize_message()
{
    uint8_t  ipv4[4] = { 127, 0, 0, 1 };
    uint64_t ipv6[2] = { 0xdeadbeef00004b1d, 0xcafed00dcafebabe };
    uint8_t  alg     = 2;

    Message msg;
    msg << ipv4 << ipv6 << alg;

    // stringyfy
    std::stringstream ss;
    ss << msg;
    return ss.str();
}

int main()
{
    std::string const rep = serialize_message();
    std::cout << rep << std::flush; // for demo (view in hex)

    // reconstruct
    Message roundtrip;
    std::istringstream(rep) >> roundtrip;

    uint8_t  ipv4[4] = { 0 };
    uint64_t ipv6[2] = { 0 };
    uint8_t  alg     = 0;
    roundtrip >> ipv4 >> ipv6 >> alg;

    assert(rep == boost::lexical_cast<std::string>(roundtrip));
}