c++ - 无需外部库的简单结构的序列化
问题描述
我正在尝试序列化像下面这样的简单单级类,没有像 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);
}
解决方案
不要擦除类型;也就是说,不要将指针指向void*
. 如果您通过模板保留指针的类型,您可以直接从它们的类型中选择反序列化函数,因此您甚至不必指定它们。实际上,您已经有一个错误,您将第二个成员标记Message2
SerInt
为String
. 如果您处理实际类型而不是强制用户复制它们,则可以避免此类错误。此外,公共超类是完全没有必要的。
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&
T
Part
// 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
.
推荐阅读
- haskell-stack - `nix-env -i stack` 在 Mac OS X 上失败
- python - 将 SQL 时间戳列转换为 Python 数据框的日期格式列
- atg - 如何编写 RQLQuery?
- python - 在python中将字符串转换为日期时间对象
- r - 使用 RDCOMClient 创建新的 Excel 应用程序
- ios - 如何在快速滑动推送和弹出视图控制器时保持导航屏幕
- javascript - VueJS 只允许基于类名选择 1 个复选框
- python - 用于在训练之外的对象级别上从语义分割评估预测边界框的度量
- selenium - 如何使用 Sibling 为下一个 Div 编写 Xpath
- android - react native 卡在配置 react-native-gesture-handler