The goal of serialization is to encode any data in a way that can be properly decoded on another system or in another application. The typical obstacles for developers are as follows:
- Platform-specific differences, such as data alignment and endianness.
- Data scattered across memory; for example, elements of a linked list can be located far away from each other. The representation of disconnected blocks linked by pointers is natural for memory but cannot be automatically translated into a sequence of bytes when transferring it to another process.
A generic approach to this problem is letting a class define the functions to convert its content into a serialized form and restore an instance of a class from the serialized form.
In our application, we will overload operator<< of the output stream and operator>> of the input stream to serialize and deserialize data respectively:
- In your ~/test working directory, create a subdirectory called stream.
- Use your favorite text editor to create a stream.cpp file in the stream subdirectory.
- Start with the definition of the data structures that you want to serialize:
#include <iostream>
#include <sstream>
#include <list>
struct Point {
int x, y;
};
struct Paths {
Point source;
std::list<Point> destinations;
};
- Next, we overload the << and >> operators that are responsible for writing and reading the Point objects into and from a stream respectively. For the Point data type enter the following:
std::ostream& operator<<(std::ostream& o, const Point& p) {
o << p.x << " " << p.y << " ";
return o;
}
std::istream& operator>>(std::istream& is, Point& p) {
is >> p.x;
is >> p.y;
return is;
}
- They are followed by the << and >> overloaded operators for the Paths objects:
std::ostream& operator<<(std::ostream& o, const Paths& paths) {
o << paths.source << paths.destinations.size() << " ";
for (const auto& x : paths.destinations) {
o << x;
}
return o;
}
std::istream& operator>>(std::istream& is, Paths& paths) {
size_t size;
is >> paths.source;
is >> size;
for (;size;size--) {
Point tmp;
is >> tmp;
paths.destinations.push_back(tmp);
}
return is;
}
- Now, let's wrap everything up in the main function:
int main(int argc, char** argv) {
Paths paths = {{0, 0}, {{1, 1}, {0, 1}, {1, 0}}};
std::stringstream in;
in << paths;
std::string serialized = in.str();
std::cout << "Serialized paths into the string: ["
<< serialized << "]" << std::endl;
std::stringstream out(serialized);
Paths paths2;
out >> paths2;
std::cout << "Original: " << paths.destinations.size()
<< " destinations" << std::endl;
std::cout << "Restored: " << paths2.destinations.size()
<< " destinations" << std::endl;
return 0;
}
- Finally, create a CMakeLists.txt file containing the build rules for our program:
cmake_minimum_required(VERSION 3.5.1)
project(stream)
add_executable(stream stream.cpp)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
SET(CMAKE_CXX_FLAGS "--std=c++11")
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabi-g++)
You can now build and run the application.