Binary Serialization can be sort of scary, maybe, to some people. Fortunately, it is super simple, straight forward, and even elegant, in C++. Binary data is the raw representation of information on the computer, and there are easy ways to work with it in C++. The benefits of using Binary are that it can save a lot of space compared to ascii, which will give speed benefits on the computer and accross the network.
Here is an example of ASCII data:
Nick is nearly 40. PI is 3.14159265359
There are two types of data here, text and number. In binary, Text looks like Text. Numbers, however, look quite different.
PI for instance, in binary, looks like the following:
ê.DTû! @
This is a double and represented by eight bytes or characters. As a float, we get four bytes or characters.
ÛI@
This is considered a non-human readable format and common editors would be hex editors. In a hex editor, the float looks like so:
DB 0F 49 40
OK, onto C++.
There are two simple says to work with Binary in C++. With streams and with strings. To start, we will examine an fstream object.
We open the fstream using the binary and write flags:
class BinaryWriter {
std::ofstream fout;
public:
BinaryWriter(const std::string& fileName) {
fout.open(fileName, std::ofstream::binary | std::ofstream::out);
}
Next, there is a straight forward templated method for primitives and simple objects:
template<typename T>
BinaryWriter& operator<<(const T& data) {
std::size_t size = sizeof(data);
for (int i = 0; i < size; ++i) {
fout.put(reinterpret_cast<const char*>(&data)[i]);
}
return *this;
}
we can overload this method for more complicated or special data types:
BinaryWriter& operator<<(const std::string& data) {
fout.write(data.data(), data.size() );
return *this;
}
Using these methods is simple and could look like so:
BinaryWriter bw("out.bin");
float pi = 3.14159265359;
bw << pi;
std::string str("nick");
bw << str;
The generated binary file looks like so:
ÛI@nick
Normally, when working with ascii files, content tends to be white-space delimited, or null-terminated. In binary, this is not the case, we can have white spaces and null characters anywhere. This means that all fields must be constant in size, the size of the field must be detailed somewhere in the file, or in a file-format.
To read the binary file, we have a straight forward solution:
class BinaryReader {
std::ifstream fin;
public:
BinaryReader(const std::string& fileName) {
fin.open(fileName, std::ifstream::binary | std::ifstream::in );
}
~BinaryReader() {
fin.close();
}
bool fail() {
return fin.fail();
}
template<typename T>
BinaryReader& operator>>( T& data) {
std::size_t size = sizeof(data);
for (int i = 0; i < size; ++i) {
fin.get(reinterpret_cast<char*>(&data)[i]);
}
return *this;
}
BinaryReader& operator>>(std::string& data) {
fin.read(data.data(), data.size());
return *this;
}
Keeping in mind that in our case the string field is a constant size which is not defined in the file, here is the reading code:
BinaryReader br("out.bin");
float pi;
br >> pi;
std::string str; str.resize(4);
br >> str;
This has been an example of working with binary using fstream. Many folks do not realize that you can also store binary data in std::string. std::string is not any sort of c-string, it can store any sort of characters. Unless you call the c_str method, you do not need to work with a c-string.
Here is an example of using std::string with c-str vs binary:
std::string stra = "\0\0\0b\0e"; //this is a c-str assignment, does not work
std::string strb = { '\0','\0','\0','b', '\0', 'e' }; //binary-ok initialization
std::cout << strb; //works with cout, writes: be, all the data is present in the string/array
As long as the string.size() is correct, all binary data in the string will be preserved, and can be worked with considering it contains binary data and like any other array. It is possible to use std::string to store simple BLOBS, or any sort of binary, for use over the network or in any other way.