TheBeardless
TheBeardless

Reputation: 67

How to serialize / deserialize INTs in C++

So, I´d want to implement simple serialization for some int variables in C++ and I really don´t know how...


My goal is the following:

I essentially want to be able to convert any integer to binary, preferably with a simple function call.

// Here´s some dummy code of what I essentially want to do

int TestVariable = 25;
String FilePath = "D:\dev\Test.txt";

Serialize(TestVariable, FilePath);

// [...] 
// at some later point in the code, when I want to access the file

Deserialize(&TestVariable, FilePath);


I already heard of libraries like Boost, but I think that´d be a bit overkill when I just want to serialize simple variables.



Already thank you in advance for your answers. :D

Upvotes: 0

Views: 1881

Answers (2)

Sam Pronee
Sam Pronee

Reputation: 91

First of all, there is a little "inconsistency": you're asking for binary serialization, in something that looks like a text file. I will assume you really want a binary output.

The only thing to take care about when serializing integers is the endianness of the machine (even though most of machines are little endian).

In C++17 or lower the easiest way is a runtime check like

inline bool littleEndian()
{
    static const uint32_t test = 0x01020304;
    return *((uint8_t *)&test) == 0x04;
}

C++20 introduces a compile-time check so you can rewrite the previous as

constexpr bool littleEndian()
{
    return std::endian::native == std::endian::little;
}

At this point what you want, is writing in a standard way all integers. Usually BigEndian is the standard.

template <typename T>
inline static T revert(T num)
{
    T res;
    for (std::size_t i = 0; i < sizeof(T); i++)
        ((uint8_t *)&res)[i] = ((uint8_t *)&num)[sizeof(T) - 1 - i];
    return res;
}

at this point your serializer would be:

template <typename T>
void serialize(T TestVariable, std::string& FilePath)
{

    static_assert(std::is_integral<T>::value);   //check that T is of {char, int, ...} type
    static_assert(!std::is_reference<T>::value); //check that T is not a reference

    std::ofstream o(FilePath);
    if (littleEndian())
        TestVariable = revert(TestVariable);
    o.write((char *)&TestVariable, sizeof(T));
}

and your deserializer would be

template <typename T>
void deserialize(T *TestVariable, std::string FilePath)
{
    static_assert(std::is_integral<T>::value);
    std::ifstream i(FilePath);
    i.read((char *)TestVariable, sizeof(T));
    if (littleEndian())
        *TestVariable = revert(*TestVariable);
}

Notice: this code is just an example that works with your interface, you just have to include <iostream>, <fstream> and if you're using the c++20 version, include <bit>

Upvotes: 2

Alessandro Teruzzi
Alessandro Teruzzi

Reputation: 3968

First let me laydown the reasons not to do this:

  • It will be not safe to reuse the files on a different machine
  • Speed could be much slower than any library
  • Complex type like pointer, maps or structure are very difficult to implement right

But if you really what to do something custom made you can simply use streams, here is an example using stringstream (I always use stringstream in my unit test because I want them to be quick), but you can simply modify it to use filestream.

Please note, the type must be default constructable to be used by deserialize template function. That must be a very stringent requirement for complex classes.

#include  <sstream>
#include  <iostream>

template<typename T>
void serialize(std::ostream& os, const T& value)
{
    os << value;
}

template<typename T>
T deserialize(std::istream& is)
{
    T value;
    is >> value;
    return value;
}

int main()
{
    std::stringstream ss;
    serialize(ss, 1353);
    serialize(ss, std::string("foobar"));

    std::cout << deserialize<int>(ss) << " " << deserialize<std::string>(ss) << std::endl;
    return 0;
 }

Upvotes: 1

Related Questions