Mercurial > code
diff C++/Pack.h @ 290:7433ebe6a8b0
Pack:
* Stateful classes
* Use << and >> operators for more convenience,
* Use PackFileWriter and PackFileReader to write/read a file,
* Use PackStringWriter and PackStringReader to write/read a string,
* Pack now supports object serialization through Pack::TypeInfo<T>::(un)serialize.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 13 Nov 2014 13:08:49 +0100 |
parents | b5d795389387 |
children | 345aaeb5e0ba |
line wrap: on
line diff
--- a/C++/Pack.h Tue Nov 11 15:05:18 2014 +0100 +++ b/C++/Pack.h Thu Nov 13 13:08:49 2014 +0100 @@ -21,7 +21,8 @@ #include <cstdint> #include <fstream> -#include <stdexcept> +#include <memory> +#include <sstream> #include <string> /** @@ -36,6 +37,25 @@ * uint64_t */ class Pack { +private: + template <typename T> + struct IsContainer + { + using Yes = char [1]; + using No = char [2]; + + template <typename U> + static Yes &test(typename U::value_type *); + + template <typename U> + static No &test(...); + + static constexpr const bool value = sizeof (test<T>(0)) == sizeof (Yes); + }; + + friend class PackWriter; + friend class PackReader; + public: /** * @enum Endian @@ -60,146 +80,311 @@ */ template <typename T> struct TypeInfo { - static const bool supported = false; + static constexpr const bool convertible{false}; + static constexpr const bool serializable{false}; + }; + + /** + * Helper to mark a specialization convertible. + * + * Already done for: + * uint8_t + * uint16_t + * uint32_t + * uint64_t + * + * The specialization must have the following function: + * + * static constexpr void convert(T &value) noexcept + */ + struct Convertible { + static constexpr const bool convertible{true}; + }; + + /** + * Helper to mark a specialization serializable. + * + * The specialisation must have the following functions: + * + * static void serialize(PackWriter &writer, const T &) + * static void unserialize(PackReader &reader, T &) + */ + struct Serializable { + static constexpr const bool serializable{true}; }; /** - * Convert data only if the requested endian is different. + * Convert data inplace. * - * @param value the value to convert if needed - * @param endian the endian mode - * @return the converted value + * @param value the value */ template <typename T> - static inline T convert(T value, Endian endian) + static inline void convert(T &value) noexcept { - static_assert(TypeInfo<T>::supported, "unsupported type"); + static_assert(TypeInfo<T>::convertible, "unsupported type"); + + TypeInfo<T>::convert(value); + } +}; + +/** + * @class PackReader + * @brief Base abstract reader class + */ +class PackReader { +protected: + Pack::Endian m_endian; + + PackReader(Pack::Endian endian); + + virtual std::istream &stream() = 0; - if (endian != mode) - return TypeInfo<T>::convert(value); +public: + /** + * Default destructor. + */ + virtual ~PackReader() = default; - return value; + /** + * Read a primitive convertible type. + * + * @param value the value destination + * @return *this + */ + template <typename T, typename std::enable_if<Pack::TypeInfo<T>::convertible>::type * = nullptr> + PackReader &operator>>(T &value) + { + stream().read(reinterpret_cast<char *>(&value), sizeof (T)); + + if (m_endian != Pack::mode) + Pack::convert(value); + + return *this; } /** - * Write nothing, stop recursion. - */ - static void write(std::ostream &, Endian) - { - } - - /** - * Write binary data to the stream. + * Read a serializable type. * - * @param out the output stream - * @param endian the endian mode - * @param args the arguments - * @throw std::runtime_exception on error + * @param value the value destination + * @return *this */ - template <typename T, typename... Args> - static void write(std::ostream &out, Endian endian, const T &value, const Args&... args) + template <typename T, typename std::enable_if<Pack::TypeInfo<T>::serializable>::type * = nullptr> + PackReader &operator>>(T &value) { - auto ret = convert(value, endian); - out.write(reinterpret_cast<const char *>(&ret), TypeInfo<T>::size); - write(out, endian, args...); - } + Pack::TypeInfo<T>::unserialize(*this, value); - /** - * Read nothing, stop recursion. - */ - static void read(std::istream &, Endian) - { + return *this; } /** - * Read binary data from the stream. + * Read an array. + * + * This operator is a little bit tricky because you don't know in + * advance how much data you want to read. Because of that, this + * function looks the capacity of the container and reads that number + * of data. * - * @param in the input stream - * @param endian the endian mode - * @param args the arguments - * @throw std::runtime_exception on error + * Because it looks for capacity, you can't use a container which + * already have some data, they will be overriden. + * + * If this is a concern, you should roll your own loop to fill up + * your container. + * + * @param container the container (all previous data will be lost) + * @return *this */ - template <typename T, typename... Args> - static void read(std::istream &in, Endian endian, T &value, Args&... args) + template <typename T, typename std::enable_if<Pack::IsContainer<T>::value>::type * = nullptr> + PackReader &operator>>(T &container) { - in.read(reinterpret_cast<char *>(&value), TypeInfo<T>::size); - value = convert(value, endian); - read(in, endian, args...); + typename T::value_type v; + + T copy; + + for (size_t i = 0; i < container.capacity(); ++i) { + (*this) >> v; + copy.push_back(v); + } + + container = std::move(copy); + + return *this; + } +}; + +/** + * @class PackWriter + * @brief Base abstract writer class + */ +class PackWriter { +protected: + Pack::Endian m_endian; + + PackWriter(Pack::Endian endian); + + virtual std::ostream &stream() = 0; + +public: + /** + * Default destructor. + */ + virtual ~PackWriter() = default; + + /** + * Write a convertible type to the stream. + * + * @param value the value + * @return *this + */ + template <typename T, typename std::enable_if<Pack::TypeInfo<T>::convertible>::type * = nullptr> + PackWriter &operator<<(T value) + { + if (m_endian != Pack::mode) + Pack::convert(value); + + stream().write(reinterpret_cast<const char *>(&value), sizeof (T)); + + return *this; + } + + /** + * Write a serializable type to the stream. + * + * @param value the value + * @return *this + */ + template <typename T, typename std::enable_if<Pack::TypeInfo<T>::serializable>::type * = nullptr> + PackWriter &operator<<(const T &value) + { + Pack::TypeInfo<T>::serialize(*this, value); + + return *this; } /** - * Read array and store it to the output iterator. Because the container - * is not allocated, we use an integer based size to determine the - * number of bytes to read. + * Write a container to the stream. * - * @param in the input stream - * @param endian the endian mode - * @param out the output it - * @param count the number of bytes to read - * @throw std::runtime_error on error + * @param container the container + * @return *this + */ + template <typename T, typename std::enable_if<Pack::IsContainer<T>::value>::type * = nullptr> + PackWriter &operator<<(const T &container) + { + for (const auto &v : container) + (*this) << v; + + return *this; + } +}; + +/** + * @class PackFileReader + * @brief Extract binary data from a file + */ +class PackFileReader : public PackReader { +private: + std::ifstream m_in; + +protected: + std::istream &stream() override; + +public: + /** + * Read a file. + * + * @param path the path + * @param endian the endian requested */ - template <typename OutputIt> - static void readArray(std::istream &in, Endian endian, OutputIt out, unsigned count) - { - typename OutputIt::container_type::value_type byte; + PackFileReader(const std::string &path, Pack::Endian endian); +}; + +/** + * @class PackStringReader + * @brief Extract binary data from a string + */ +class PackStringReader : public PackReader { +private: + std::istringstream m_in; + + std::istream &stream() override; + +public: + /** + * Read a string. + * + * @param input the input string + * @param endian the endian requested + */ + PackStringReader(std::string input, Pack::Endian endian); +}; + +/** + * @class PackFileWriter + * @brief Write binary data to a string + */ +class PackFileWriter : public PackWriter { +private: + std::ofstream m_out; - for (unsigned i = 0; i < count; ++i) { - read(in, endian, byte); - *out++ = byte; - } - } +protected: + std::ostream &stream() override; + +public: + /** + * Write to a file. + * + * @param path the path + * @param endian the endian requested + */ + PackFileWriter(const std::string &path, Pack::Endian endian); +}; + +/** + * @class PackStringWriter + * @brief Write binary data to a string + */ +class PackStringWriter : public PackWriter { +private: + std::ostringstream m_out; + + std::ostream &stream() override; + +public: + /** + * Write to a string + * + * @param endian the endian requested + */ + PackStringWriter(Pack::Endian endian); /** - * Read a container from an input iterator and write it to output stream. + * The current buffer. Returns a copy of the string. * - * @param out the output stream - * @param endian the endian mode - * @param in the input iterator - * @param count the number of bytes to write - * @throw std::runtime_error on error + * @return the string */ - template <typename InputIt> - static void writeArray(std::ostream &out, Endian endian, InputIt in, unsigned count) - { - typename std::iterator_traits<InputIt>::value_type byte; + std::string buffer() const; +}; - for (unsigned i = 0; i < count; ++i) { - byte = *in++; - write(out, endian, byte); - } +template <> +struct Pack::TypeInfo<uint8_t> : public Pack::Convertible { + static constexpr void convert(uint8_t &) noexcept + { + // uint8_t are endian independent } }; template <> -struct Pack::TypeInfo<uint8_t> { - static constexpr const bool supported = true; - static constexpr const size_t size = sizeof (uint8_t); - - static constexpr uint8_t convert(uint8_t v) +struct Pack::TypeInfo<uint16_t> : public Pack::Convertible { + static constexpr void convert(uint16_t &v) { - return v; + v = (((v >> 8) & 0x00FFL) | ((v << 8) & 0xFF00L)); } }; template <> -struct Pack::TypeInfo<uint16_t> { - static constexpr const bool supported = true; - static constexpr const size_t size = sizeof (uint16_t); - - static constexpr uint16_t convert(uint16_t v) +struct Pack::TypeInfo<uint32_t> : public Pack::Convertible { + static constexpr void convert(uint32_t &v) { - return (((v >> 8) & 0x00FFL) | ((v << 8) & 0xFF00L)); - } -}; - -template <> -struct Pack::TypeInfo<uint32_t> { - static constexpr const bool supported = true; - static constexpr const size_t size = sizeof (uint32_t); - - static constexpr uint32_t convert(uint32_t v) - { - return ((((v) >> 24) & 0x000000FFL) + v = ((((v) >> 24) & 0x000000FFL) | (((v) >> 8) & 0x0000FF00L) | (((v) << 8) & 0x00FF0000L) | (((v) << 24) & 0xFF000000L)); @@ -207,13 +392,10 @@ }; template <> -struct Pack::TypeInfo<uint64_t> { - static constexpr const bool supported = true; - static constexpr const size_t size = sizeof (uint64_t); - - static constexpr uint64_t convert(uint64_t v) +struct Pack::TypeInfo<uint64_t> : public Pack::Convertible { + static constexpr void convert(uint64_t &v) { - return ((((v) & 0xff00000000000000ull) >> 56) + v = ((((v) & 0xff00000000000000ull) >> 56) | (((v) & 0x00ff000000000000ull) >> 40) | (((v) & 0x0000ff0000000000ull) >> 24) | (((v) & 0x000000ff00000000ull) >> 8 )