如何在不使用外部库的情况下序列化/扁平化指定C++ node结构体为字节数组或其他
Alright, let's break down how to serialize and deserialize your node struct without any external libraries. Since we're rolling our own solution, we need to handle every data type manually—strings, booleans, vectors, and those recursive node pointers—by writing/reading bytes directly to/from a buffer. Here's a practical, step-by-step approach with code examples:
Before we jump into code, let's outline how we'll handle each data type:
- Strings: First write the length of the string (as a fixed-size integer like
uint32_tto avoid overflow), then write the string's raw bytes. - Booleans: Write a single byte (0 for
false, 1 fortrue). - Vectors: First write the number of elements, then serialize each element one by one.
- Recursive Nodes: Since we have
vector<node*>, we'll handle null pointers explicitly (write a boolean flag first to mark if the pointer is null), then recursively serialize each non-null node.
We'll use a std::vector<uint8_t> as our buffer—it's dynamic, easy to work with, and gives us a raw byte array we can save or transmit.
Here's the code with helper functions and recursive serialization:
#include <vector> #include <string> #include <cstdint> struct node { std::string splitOn; std::string label; bool isLeaf; std::vector<std::string> childrenValues; std::vector<node*> children; }; // Helper to write a primitive type (like uint32_t, bool) to the buffer template<typename T> void writeToBuffer(std::vector<uint8_t>& buffer, const T& value) { const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&value); buffer.insert(buffer.end(), bytes, bytes + sizeof(T)); } // Helper to serialize a std::string void writeString(std::vector<uint8_t>& buffer, const std::string& str) { uint32_t len = static_cast<uint32_t>(str.size()); writeToBuffer(buffer, len); buffer.insert(buffer.end(), str.begin(), str.end()); } // Recursive function to serialize a single node (handles null pointers) void serializeNode(const node* n, std::vector<uint8_t>& buffer) { if (!n) { bool isNull = true; writeToBuffer(buffer, isNull); return; } bool isNull = false; writeToBuffer(buffer, isNull); // Serialize each field in order writeString(buffer, n->splitOn); writeString(buffer, n->label); writeToBuffer(buffer, n->isLeaf); // Serialize childrenValues vector uint32_t cvCount = static_cast<uint32_t>(n->childrenValues.size()); writeToBuffer(buffer, cvCount); for (const auto& val : n->childrenValues) { writeString(buffer, val); } // Serialize children nodes (recursive call) uint32_t childCount = static_cast<uint32_t>(n->children.size()); writeToBuffer(buffer, childCount); for (const auto& child : n->children) { serializeNode(child, buffer); } } // Top-level function to start serialization from the root node std::vector<uint8_t> serialize(const node& root) { std::vector<uint8_t> buffer; serializeNode(&root, buffer); return buffer; }
To get our node tree back from the byte array, we'll reverse the serialization process. We'll use an index tracker to keep track of where we are in the buffer, and recursively rebuild each node.
Here's the corresponding deserialization code:
// Helper to read a primitive type from the buffer template<typename T> T readFromBuffer(const std::vector<uint8_t>& buffer, size_t& idx) { T value = *reinterpret_cast<const T*>(&buffer[idx]); idx += sizeof(T); return value; } // Helper to deserialize a std::string std::string readString(const std::vector<uint8_t>& buffer, size_t& idx) { uint32_t len = readFromBuffer<uint32_t>(buffer, idx); std::string str(buffer.begin() + idx, buffer.begin() + idx + len); idx += len; return str; } // Recursive function to deserialize a single node (handles null pointers) node* deserializeNode(const std::vector<uint8_t>& buffer, size_t& idx) { bool isNull = readFromBuffer<bool>(buffer, idx); if (isNull) { return nullptr; } node* n = new node(); // Deserialize each field in the same order as serialization n->splitOn = readString(buffer, idx); n->label = readString(buffer, idx); n->isLeaf = readFromBuffer<bool>(buffer, idx); // Deserialize childrenValues vector uint32_t cvCount = readFromBuffer<uint32_t>(buffer, idx); for (uint32_t i = 0; i < cvCount; ++i) { n->childrenValues.push_back(readString(buffer, idx)); } // Deserialize children nodes (recursive call) uint32_t childCount = readFromBuffer<uint32_t>(buffer, idx); for (uint32_t i = 0; i < childCount; ++i) { n->children.push_back(deserializeNode(buffer, idx)); } return n; } // Top-level function to start deserialization from the byte buffer node* deserialize(const std::vector<uint8_t>& buffer) { size_t idx = 0; return deserializeNode(buffer, idx); }
- Endianness: The code uses your system's native endianness. If you need cross-platform compatibility (e.g., serialize on x86 and deserialize on ARM), add code to convert between host byte order and network byte order (use
htonl/ntohlforuint32_tvalues). - Memory Leaks: The deserialization code uses
newto create nodes. Add this helper function to recursively delete all nodes when you're done:void deleteNodeTree(node* root) { if (!root) return; for (auto child : root->children) { deleteNodeTree(child); } delete root; } - Cycle Detection: If your node graph has cycles (e.g., a child node pointing back to its parent), the current serialization code will loop infinitely. To fix this, track serialized nodes with an ID map—assign each node a unique ID, and write the ID instead of re-serializing the node if it's already been processed.
- String Encoding: The code assumes
std::stringstores raw bytes, which works for most use cases. If you need explicit handling of multi-byte encodings (like UTF-8), you don't need to change anything—UTF-8 is just bytes, so it'll serialize/deserialize correctly.
内容的提问来源于stack exchange,提问作者Kumar Roshan Mehta

