Monday, December 16, 2024

Serialization

In C++, to serialize simple data, aka plain old data (POD), where the layout in memory is predictable, you can use a char* (byte) buffer:


// Serialization for simple data structures (POD) with C++
// Şamil Korkmaz, 16.12.2024
#include <iostream>
#include <cstring> // For memcpy
// Simple POD structure
struct MyStruct {
int id;
double value;
char name[50];
};
void serializeToBuffer(const MyStruct& obj, char* buffer) {
std::memcpy(buffer, &obj, sizeof(MyStruct));
}
void deserializeFromBuffer(const char* buffer, MyStruct& obj) {
std::memcpy(&obj, buffer, sizeof(MyStruct));
}
int main() {
MyStruct original = {42, 3.14, "Example"};
char buffer[sizeof(MyStruct)];
serializeToBuffer(original, buffer);
MyStruct deserialized;
deserializeFromBuffer(buffer, deserialized);
// Output the deserialized structure
std::cout << "Deserialized Object:\n";
std::cout << "ID: " << deserialized.id << "\n";
std::cout << "Value: " << deserialized.value << "\n";
std::cout << "Name: " << deserialized.name << "\n";
return 0;
}

This method cannot be used for non-POD types (e.g., those with pointers or virtual methods) because their memory layout is not portable. Examples are std::string, std::vector. For such types, you can use std::ostringstream:


// Serialization for non-POD (e.g. std::string, std::vector) with C++
// Şamil Korkmaz, 16.12.2024
#include <sstream> // Required for std::ostringstream and std::istringstream
#include <iostream>
#include <vector>
#include <string>
#include <cstring> // For memcpy
struct MyStruct {
int id;
double value;
std::string name;
std::vector<int> numbers;
void serialize(std::ostream& out) const {
out.write(reinterpret_cast<const char*>(&id), sizeof(id));
out.write(reinterpret_cast<const char*>(&value), sizeof(value));
// Serialize std::string: First the string size, followed by the characters
size_t nameSize = name.size();
out.write(reinterpret_cast<const char*>(&nameSize), sizeof(nameSize));
out.write(name.data(), nameSize);
// Serialize std::vector: First the vector size, followed by its elements
size_t vecSize = numbers.size();
out.write(reinterpret_cast<const char*>(&vecSize), sizeof(vecSize));
out.write(reinterpret_cast<const char*>(numbers.data()), vecSize * sizeof(int));
}
void deserialize(std::istream& in) {
in.read(reinterpret_cast<char*>(&id), sizeof(id));
in.read(reinterpret_cast<char*>(&value), sizeof(value));
// Deserialize std::string: Resize the string and read the characters into it
size_t nameSize;
in.read(reinterpret_cast<char*>(&nameSize), sizeof(nameSize));
name.resize(nameSize);
in.read(&name[0], nameSize);
// Deserialize std::vector: Resize the vector and read its elements
size_t vecSize;
in.read(reinterpret_cast<char*>(&vecSize), sizeof(vecSize));
numbers.resize(vecSize);
in.read(reinterpret_cast<char*>(numbers.data()), vecSize * sizeof(int));
}
};
int main() {
MyStruct original = {42, 3.14, "Example", {1, 2, 3, 4, 5}};
std::ostringstream out; // Ensure <sstream> is included
original.serialize(out);
std::istringstream in(out.str()); // Ensure <sstream> is included
MyStruct deserialized;
deserialized.deserialize(in);
std::cout << "Deserialized Object:\n";
std::cout << "ID: " << deserialized.id << "\n";
std::cout << "Value: " << deserialized.value << "\n";
std::cout << "Name: " << deserialized.name << "\n";
std::cout << "Numbers: ";
for (int num : deserialized.numbers) {
std::cout << num << " ";
}
std::cout << "\n";
return 0;
}
Both approaches assume that the serialized data format and endianness match between serialization and deserialization. For more complex cases, use libraries like nlohmann/json for JSON-based serialization and Boost.Serialization for binary/text serialization with more features.

No comments:

Post a Comment