I am currently working on a binary serialization library written in C++11 for a personal project. I'd really like to have a review about my design, my implementation and everything else. The library is inspired from QDataStream
.
Here is the GitLab repository containing the project.
Sery/Misc.hh
#ifndef SERY_MISC_HH_
#define SERY_MISC_HH_
#define SERY_BEGIN_NAMESPACE namespace Sery {
#define SERY_END_NAMESPACE }
#include <cstdint>
#include <type_traits>
SERY_BEGIN_NAMESPACE
template< bool B, class T = void >
using enable_if_t = typename ::std::enable_if<B, T>::type;
enum Endian
{
LittleEndian,
BigEndian
};
typedef std::int8_t int8;
typedef std::int16_t int16;
typedef std::int32_t int32;
typedef std::int64_t int64;
typedef std::uint8_t uint8;
typedef std::uint16_t uint16;
typedef std::uint32_t uint32;
typedef std::uint64_t uint64;
namespace detail
{
inline Endian getSoftwareEndian()
{
int16 witness = 0x5501;
int8 test = *((int8*)&witness);
return (test == 1 ? Endian::LittleEndian : Endian::BigEndian);
}
}
SERY_END_NAMESPACE
#endif // SERY_MISC_HH_
Sery/Buffer.hh
#ifndef SERY_BUFFER_HH_
#define SERY_BUFFER_HH_
#include <vector>
#include <Sery/IBuffer.hh>
SERY_BEGIN_NAMESPACE
class Buffer : public IBuffer
{
public:
Buffer();
Buffer(const char *buffer, uint32 size);
~Buffer();
public:
virtual void writeRaw(const char *buffer, uint32 size) final;
virtual void readRaw(char *buffer, uint32 size) final;
virtual uint32 size() const final;
virtual const char *data() const final;
private:
std::vector<char> _buffer;
};
SERY_END_NAMESPACE
#endif // SERY_BUFFER_HH_
Sery/Buffer.cpp
#include <Sery/Buffer.hh>
#include <sstream>
#include <iomanip>
#include <string.h>
SERY_BEGIN_NAMESPACE
Buffer::Buffer()
: _buffer()
{
}
Buffer::Buffer(const char *buffer, uint32 size)
: _buffer(buffer, buffer + size)
{
}
Buffer::~Buffer()
{
}
void Buffer::writeRaw(const char *buffer, uint32 size)
{
_buffer.insert(_buffer.end(), buffer, buffer + size);
}
void Buffer::readRaw(char *buffer, uint32 size)
{
memcpy(buffer, _buffer.data(), size);
_buffer.erase(_buffer.begin(), _buffer.begin() + size);
}
uint32 Buffer::size() const
{
return ((uint32)_buffer.size());
}
const char *Buffer::data() const
{
return (_buffer.data());
}
SERY_END_NAMESPACE
Sery/Stream.hh
#ifndef SERY_STREAM_HH_
#define SERY_STREAM_HH_
#include <Sery/Misc.hh>
SERY_BEGIN_NAMESPACE
class IBuffer;
class Stream
{
private:
// Removing copy and move functions
Stream(const Stream&) = delete;
Stream(Stream&&) = delete;
Stream &operator=(const Stream&) = delete;
Stream &operator=(Stream&&) = delete;
private:
static Endian globalEndian;
public:
static void setGlobalEndian(Endian endian);
static Endian getGlobalEndian();
public:
// Must be constructed with an IBuffer
Stream(IBuffer &buffer);
~Stream();
public:
// Proxies for lower level manipulation
Stream &writeRaw(const char *buffer, uint32 size);
Stream &readRaw(char *buffer, uint32 size);
public:
void setLocalEndian(Endian endian);
Endian getLocalEndian() const;
private:
IBuffer &_buffer;
Endian _localEndian;
};
// (De)Serialization of a C-Style string
Stream &operator<<(Stream &stream, const char *str);
Stream &operator>>(Stream &stream, char *&str);
// (De)Serialization of all arithmetic types
template <class T, enable_if_t<std::is_arithmetic<T>::value> * = nullptr>
Stream &operator<<(Stream &stream, T t);
template <class T, enable_if_t<std::is_arithmetic<T>::value> * = nullptr>
Stream &operator>>(Stream &stream, T &t);
SERY_END_NAMESPACE
#include "detail/Stream_STD.hh" // Contains serialization for standard library
#endif // SERY_STREAM_HH_
Sery/Stream.cpp
#include <Sery/Stream.hh>
#include <Sery/IBuffer.hh>
#include <cstring>
#include <iostream>
SERY_BEGIN_NAMESPACE
Stream::Stream(IBuffer &buffer)
: _buffer(buffer),
_localEndian(Stream::globalEndian)
{
}
Stream::~Stream()
{
}
Stream &Stream::writeRaw(const char *buffer, uint32 size)
{
_buffer.writeRaw(buffer, size);
return (*this);
}
Stream &Stream::readRaw(char *buffer, uint32 size)
{
_buffer.readRaw(buffer, size);
return (*this);
}
void Stream::setLocalEndian(Endian endian)
{
_localEndian = endian;
}
Endian Stream::getLocalEndian() const
{
return (_localEndian);
}
// Static members
Endian Stream::globalEndian = Endian::BigEndian;
void Stream::setGlobalEndian(Endian endian)
{
globalEndian = endian;
}
Endian Stream::getGlobalEndian()
{
return (globalEndian);
}
//////////////////////////////////////////
// External functions for serialization //
//////////////////////////////////////////
// Templates for serializing arithmetics types
template <class T, enable_if_t<std::is_arithmetic<T>::value> *>
Stream &operator<<(Stream &stream, T t)
{
Endian softwareEndian = detail::getSoftwareEndian();
Endian currentEndian = stream.getLocalEndian();
char buffer[sizeof(T)];
const uint8 *p = reinterpret_cast<const uint8 *>(&t);
for (size_t index = 0;
index < sizeof(T);
++index)
{
if (currentEndian == softwareEndian)
buffer[index] = *p++;
else
buffer[sizeof(T) - index - 1] = *p++;
}
stream.writeRaw(buffer, sizeof(T));
return (stream);
}
// Explicit instantiations of templates functions
template Stream &operator<< <int8> (Stream &, int8);
template Stream &operator<< <int16> (Stream &, int16);
template Stream &operator<< <int32> (Stream &, int32);
template Stream &operator<< <int64> (Stream &, int64);
template Stream &operator<< <uint8> (Stream &, uint8);
template Stream &operator<< <uint16> (Stream &, uint16);
template Stream &operator<< <uint32> (Stream &, uint32);
template Stream &operator<< <uint64> (Stream &, uint64);
template Stream &operator<< <float> (Stream &, float);
template Stream &operator<< <double> (Stream &, double);
template Stream &operator<< <long double> (Stream &, long double);
// Templates for deserializing arithmetics types
template <class T, enable_if_t<std::is_arithmetic<T>::value> *>
Stream &operator>>(Stream &stream, T &t)
{
Endian softwareEndian = detail::getSoftwareEndian();
Endian currentEndian = stream.getLocalEndian();
char buffer[sizeof(T)];
stream.readRaw(buffer, sizeof(T));
uint8 *p = reinterpret_cast<uint8 *>(&t);
for (size_t index = 0;
index < sizeof(T);
++index)
{
if (currentEndian == softwareEndian)
*p++ = buffer[index];
else
*p++ = buffer[sizeof(T) - index - 1];
}
return (stream);
}
// Explicit instantiations of templates functions
template Stream &operator>> <int8> (Stream &, int8 &);
template Stream &operator>> <int16> (Stream &, int16 &);
template Stream &operator>> <int32> (Stream &, int32 &);
template Stream &operator>> <int64> (Stream &, int64 &);
template Stream &operator>> <uint8> (Stream &, uint8 &);
template Stream &operator>> <uint16> (Stream &, uint16 &);
template Stream &operator>> <uint32> (Stream &, uint32 &);
template Stream &operator>> <uint64> (Stream &, uint64 &);
template Stream &operator>> <float> (Stream &, float &);
template Stream &operator>> <double> (Stream &, double &);
template Stream &operator>> <long double> (Stream &, long double &);
// (De)Serialization of C-Style strings
Stream &operator<<(Stream &stream, const char *str)
{
uint32 len = (uint32)std::strlen(str) + 1;
stream << len;
stream.writeRaw(str, len);
return (stream);
}
Stream &operator>>(Stream &stream, char *&str)
{
uint32 len = 0;
stream >> len;
char *buffer = new char[len];
stream.readRaw(buffer, len);
str = buffer;
return (stream);
}
SERY_END_NAMESPACE
htonl()
and family. In addition toEndianess
usally floating point values are not stored in IEEE-754 Interchange formats internally. They are usually IEEE-754 but one of the "Extended precision formats" and you can not translate directly to other platforms binary formats. You should convert to one of the standard Interchange formats before writing to your stream. \$\endgroup\$long double
is 128 bits but only 96 bits are used (because that is the size of the the FP register. On the virtual unix machine I run on my mac. The `long double is 128 bits. But only 80 bits are used. See: github.com/Loki-Astari/ThorsSerializer/blob/master/src/… \$\endgroup\$Sery::globalEndian
beingBigEndian
by default. \$\endgroup\$std::numeric_limits<T>::is_iec559
. Then check the size of your float withsizeof()
then check and the number of digits.std::numeric_limits<T>::digits
. With the size and number of digits you can check if is an IEEE-754 interchange format. If it is then you don't need to do anything. If it is not then unless you really want the support just throw an error. This will wolve 99.99% of situations. \$\endgroup\$