0

我正在尝试序列化像下面这样的简单单级类,没有像 boost 这样的外部库,也不必为每个类实现序列化器函数。尽管我的类很少,我可以轻松地为每个类实现一个序列化程序,但为了将来参考,我希望手头有一个可以很好地扩展的简单解决方案。

每个类被序列化的要求是它的成员只能是可序列化的类型,并且定义了一个成员指针数组,这样在序列化时,无论传递哪个类,都可以迭代成员。

问题是编译失败是因为缺少成员指针被取消引用的强制转换,显然:

esp32.ino: 122:35: error: 'footprint.MessageFootprint<1>::Members[i]' 不能用作成员指针,因为它的类型是 'void*

我不知道如何将成员指针存储在可迭代集合中或如何避免强制转换void*。这就是我的目标。我想在具有单个通用序列化函数的序列化时迭代类成员。我不知道该怎么办。

enum SerializableDataTypes {
    SerInt,
    SerFloat,
    SerString,
    SerIntArray
};

template <int N>
struct MessageFootprint {
    SerializableDataTypes DataTypes[N];
    void* Members[N];
};

template<typename T, typename R>
void* void_cast(R(T::*m))
{
    union
    {
        R(T::*pm);
        void* p;
    };
    pm = m;
    return p;
}

class ControlMessage{};

// first structure to be serialized
class Message1 : public ControlMessage {
public:
    int prop1;
    int prop2;
};
const int Message1MemberCount = 2;
const MessageFootprint<Message1MemberCount> Message1FootPrint = { { SerInt, SerInt }, {void_cast(&Message1::prop1), void_cast(&Message1::prop2)} };

// second structure to be serialized
class Message2 : public ControlMessage {
public:
    int prop1;
    String prop2;
};
const int Message2MemberCount = 2;
const MessageFootprint<Message2MemberCount> Message2FootPrint = { { SerInt, SerInt }, {void_cast(&Message2::prop1), void_cast(&Message2::prop2)} };

template<int N>
void SerializeMessage(MessageFootprint<N> footprint, ControlMessage message) {
    for (int i = 0; i < N; i++) {
        if (footprint.DataTypes[i] == SerInt) {
            // serialization code here based on data type
            // for demonstration purposes it's only written in the serial port
            logLine(String(i));
            Serial.println(*((int*)(message.*(footprint.Members[i]))));
        }
    }
}

void main() {
    // usage example
    Message1 msg = Message1();
    msg.prop1 = 1;
    msg.prop2 = 2;
    SerializeMessage(Message1FootPrint, msg);
}
4

2 回答 2

1

不要擦除类型;也就是说,不要将指针指向void*. 如果您通过模板保留指针的类型,您可以直接从它们的类型中选择反序列化函数,因此您甚至不必指定它们。实际上,您已经有一个错误,您将第二个成员标记Message2 SerIntString. 如果您处理实际类型而不是强制用户复制它们,则可以避免此类错误。此外,公共超类是完全没有必要的。

template<typename T, typename... Parts>
struct MessageFootprint {
    std::tuple<Parts T::*...> parts;
    MessageFootprint(Parts T::*... parts) : parts(parts...) { }
};
template<typename T, typename... Parts>
MessageFootprint(Parts T::*...) -> MessageFootprint<T, Parts...>; // deduction guide

// e.g.
struct Message1 {
    int prop1;
    int prop2;
};
inline MessageFootprint footprint1(&Message1::prop1, &Message1::prop2);
// deduction guide allows type of footprint1 to be inferred from constructor arguments
// it is actually MessageFootprint<Message1, int, int>
// if you are on a C++ standard old enough to not have deduction guides,
// you will have to manually specify them
// this is still better than letting the types be erased, because now the compiler
// will complain if you get it wrong

// e.g. if I replicate your mistake
struct Message2 {
    int prop1;
    std::string prop2;
};
inline MessageFootprint<Message2, int, int> footprint2(&Message2::prop1, &Message2::prop2);
// This does not go through because    ^^^ is wrong

序列化可能最好通过重载来处理。对于 a 中的每个,从Part T::*aMessageFootprint<T, Part...>中提取 a并调用一个重载函数,该函数根据 决定做什么:Part&TPart

// I have no idea what serial port communication stuff you're doing
// but this gets the point across
void SerializeAtom(int i) { std::cout << "I" << i; }
void SerializeAtom(std::string const &s) { std::cout << "S" << s.size() << "S" << s; }

template<typename T, typename... Parts>
void SerializeFootprint(MessageFootprint<T, Parts...> footprint, T const &x) {
    // calls the provided functor with the things in the tuple
    std::apply(
        // this lambda is a template with its own Parts2... template parameter pack
        // and the argument is really Parts2... parts
        // we then do a fold expression over parts
        // we need std::apply because there's no simpler way to get the actual
        // values out (std::get fails when there are duplicates)
        [&x](auto... parts) { (SerializeAtom(x.*parts), ...); },
        footprint.parts);
}
// Trying to write ^^^ before C++17 would probably be a nightmare

这个系统是可扩展的:添加一个新的“原子”类型,只是重载SerializeAtom。无需管理enum或诸如此类。DeserializeAtom反序列化将意味着写入给定引用的一系列重载,并且 aDeserializeFootprint可能看起来完全像SerializeFootprint.

神螺栓演示

于 2019-12-25T21:15:18.400 回答
0

我开发了一个使用缓冲的序列化系统。

每个对象都从声明函数的接口继承:
1. 返回流上对象的大小。
2. 将对象成员存储到缓冲区。
3. 从缓冲区加载对象成员。

该系统基于结构和类可以包含填充以及类/结构最了解其成员这一事实。例如,一个多字节整数在缓冲区中可能是Big Endian,而对象需要转换为Little Endian。该系统还适用于编写可变长度文本字段的不同方法。

class Binary_Stream_Interface:
{
  public:  
      // Returns the size, in uint8_t units, that the object occupies in
      // a buffer (stream), packed.
      virtual size_t    size_on_stream() const = 0; 

      // Loads the class members from a buffer, pointed to by p_buffer.
      // The p_buffer pointer will be incremented after loading the object.
      virtual void      load_from_buffer(uint8_t* &  p_buffer) = 0;

      // Stores the class members to a buffer, pointed to by p_buffer.
      // The p_buffer pointer will be incremented after loading the object.
      virtual void      store_to_buffer(uint8_t * &  p_buffer) const = 0;
};

序列化(写入)对象:
1. 调用 size_on_stream() 以确定所需的缓冲区大小。
2. 分配缓冲区。
3.调用store_to_buffer将对象存入缓冲区。
4. 使用 . 将缓冲区写入流std::ostream::write
5. 删除缓冲区。

读取对象:
1. 调用 size_on_stream() 来确定所需的缓冲区大小。
2. 分配缓冲区。
3. 将流中的数据读入缓冲区,使用std::istream::read所需的大小。
4. 调用 load_from_buffer() 方法。
5. 删除缓冲区。

实施留给 OP 练习。

注意:模板可用于常见的 POD,std:string并使一切更加统一。

编辑 1:示例

struct Student
: public Binary_Stream_Interface
{
     std::string name;
     unsigned int id;

     size_t size_on_stream() const
     {
         size_t stream_size = sizeof(id) + sizeof(int) + name.length();
         return stream_size;
     }

     void load_from_buffer(uint8_t* & p_buffer)
     {
         // Read the string size.
         unsigned int length = *((unsigned int *)(p_buffer));
         p_buffer += sizeof(length);

         // Load the string text from the buffer
         name = std::string((char *) p_buffer, length);
         p_buffer += length;

         id = *((unsigned int *) p_buffer);
         p_buffer += sizeof(id);
    }

    void store_to_buffer(uint8_t * & p_buffer) const
    {
        unsigned int length = name.length();
        *((unsigned int *) p_buffer) = length;
        p_buffer += sizeof(unsigned int);

        p_char_buffer = (char *) p_buffer;
        std::copy(name.begin(), name.end(), p_char_buffer);
        p_buffer += length;

        *((unsigned int *) p_buffer) = id;
        p_buffer += sizeof(unsigned int);
    }
};
于 2019-12-25T21:31:35.990 回答