まえがき
C++で任意のデータをシリアライズするためのクラスの実装方法を紹介します。
今回は前回に引き続きバイナリ形式でのシリアライズ処理の実装について説明します。
ソース一式はGitHubで公開しています。
前提条件
今回実装したソースは以下の環境でビルド、動作確認しています。
実装
バイナリ形式シリアライズクラスでは、任意のデータ型をバイナリ形式(バイト配列)に変換して、アーカイブに書き込んでいく処理を行います。
ここでは、クラスの構成要素ごとに詳しく説明していきます。
メンバ変数
まず、メンバ変数として現在の実行環境のエンディアンと、シリアライズ後のデータのエンディアンを保持するようにしています。
endian::EndianType m_CurrentEndian;
endian::EndianType m_TargetEndian;
一般的なPCなどで使用しているx86/x64系 CPUのエンディアンはリトルエンディアンですが、ネットワークで送受信するときのデータはビッグエンディアンを指定することが多く、また、マイコンなどの環境でもエンディアンが異なる可能性があるため、異なる環境間でも正しくシリアライズ/デシリアライズできるようにするためです。
コンストラクタ
シリアライズクラスは複数インスタンスを生成する必要がないため、シングルトンとして実装しています。
また、コンストラクタでは現在の環境のエンディアンおよびシリアライズ後のエンディアンを保持するメンバ変数を初期化しています。
public:
static BinarySerialization& GetInstance()
{
static BinarySerialization instance;
return instance;
}
private:
BinarySerialization()
: m_CurrentEndian(endian::GetEnvironmentEndian())
, m_TargetEndian(endian::EndianType::Network)
{
}
~BinarySerialization() = default;
public:
BinarySerialization(const BinarySerialization&) = delete;
BinarySerialization(BinarySerialization&&) = delete;
BinarySerialization& operator=(const BinarySerialization&) = delete;
BinarySerialization& operator=(BinarySerialization&&) = delete;
現在の環境のエンディアンはendian::GetEnvironmentEndian()
で自動的に判定されるようにしており、シリアライズ後のエンディアンはネットワークで送受信するときに一般的に指定されるビッグエンディアンを指定しています。
endian::GetEnvironmentEndian()
の詳細についてはGitHubのソースをご参照ください。
インタフェース
インタフェース関数として、シリアライズ後のデータサイズを算出するCalculateSize()
、入力データをシリアライズしてアーカイブに変換するSerialize()
、アーカイブをデシリアライズして出力データに変換するDeserialize()
を定義します。
public:
template <typename T>
void CalculateSize(const T& in_data, size_t& out_size)
{
this->Calculate(in_data, out_size);
}
template <typename T>
void Serialize(const T& in_data, Archive& out_archive)
{
size_t size = 0;
this->CalculateSize(in_data, size);
out_archive.Reserve(size);
size_t offset = 0;
this->Serialize(in_data, offset, out_archive);
}
template <typename T>
void Deserialize(const Archive& in_archive, T& out_data)
{
size_t offset = 0;
this->Deserialize(in_archive, offset, out_data);
}
CalculateSize()
では指定されたデータ型に応じたデータサイズを算出するCalculate()
を呼び出します。構造体型などの場合は各メンバに対してCalculate()
を呼び出し、トータルのデータサイズを算出します。
Serialize()
ではCalculateSize()
を呼び出してシリアライズ後のデータサイズを算出し、アーカイブのReserve()
を呼び出してデータサイズ分のメモリを確保します。その後、指定されたデータ型に応じたシリアライズ(バイナリ形式に変換し、アーカイブへの書き込み)を行うSerialize()
を呼び出します。構造体型などの場合は各メンバに対してSerialize()
を呼び出し、すべてのデータをシリアライズします。
Deserialize()
では、指定されたデータ型に応じたDeserialize()
を呼び出してデシリアライズ(アーカイブからバイナリデータを呼び出し、元のデータに復元)します。構造体型などの場合は各メンバに対してDeserialize()
を呼び出し、すべてのメンバをデシリアライズします。
内部関数
プライベートメンバ関数としては、データ型に応じたCalculate()
、Serialize()
、Deserialize()
を実装します。
データ型に応じた処理内容を詳しく説明していきます。
算術型
算術型(論理型、整数型、浮動小数型)では、そのままバイナリ(バイト配列)との変換が可能なため、バイナリ(バイト配列)と相互変換するための処理を実装します。
template<typename T, type_traits::concept_t<std::is_arithmetic<T>::value> = nullptr>
void Calculate(const T& in_data, size_t& out_size)
{
out_size += sizeof(in_data);
}
template<typename T, type_traits::concept_t<std::is_arithmetic<T>::value> = nullptr>
void Serialize(const T& in_data, size_t& offset, Archive& out_archive)
{
std::array<byte_t, sizeof(T)> in_bytes{};
binary::BinaryConverter::Convert(this->m_CurrentEndian, this->m_TargetEndian, in_data, in_bytes);
out_archive.Write(in_bytes, offset);
}
template<typename T, type_traits::concept_t<std::is_arithmetic<T>::value> = nullptr>
void Deserialize(const Archive& in_archive, size_t& offset, T& out_data)
{
std::array<byte_t, sizeof(T)> out_bytes{};
in_archive.Read(out_bytes, offset);
binary::BinaryConverter::Convert(this->m_CurrentEndian, this->m_TargetEndian, out_bytes, out_data);
}
Calculate()
では指定されたデータ型のサイズをsizeof
で算出し、加算します。
Serialize()
ではbinary::BinaryConverter::Convert()
でエンディアンを考慮したバイト配列に変換し、アーカイブのWrite()
を呼び出してバイト配列を書き込みます。
Deserialize()
では、アーカイブのRead()
を呼び出してバイト配列にバイナリデータを読み込み、binary::BinaryConverter::Convert()
でエンディアンを考慮して指定されたデータ型に復元します。
binary::BinaryConverter::Convert()
の詳細についてはGitHubのソースをご参照ください。
列挙型
列挙型(特にenum class)では、直接バイナリ形式に変換できないため、基底型に変換して処理を行うようにしています。
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Calculate(const T& in_data, size_t& out_size)
{
this->Calculate(type_traits::underlying_cast(in_data), out_size);
}
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Serialize(const T& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(type_traits::underlying_cast(in_data), offset, out_archive);
}
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Deserialize(const Archive& in_archive, size_t& offset, T& out_data)
{
type_traits::underlying_type_t<T> data{};
this->Deserialize(in_archive, offset, data);
out_data = static_cast<T>(data);
}
Calculate()
およびSerialize()
では、type_traits::underlying_cast
を用いて基底型(デフォルトではint
)に変換してCalculate()
、Serialize()
を呼び出しています。
Deserialize()
では、type_traits::underlying_type_t<T>
で基底型の一時変数を用意し、基底型に対するDeserialize()
を呼び出してから元のデータ型にstatic_cast
で変換しています。
type_traits::underlying_cast
およびtype_traits::underlying_type_t<T>
の詳細についてはGitHubのソースをご参照ください。
STLコンテナ(std::array
、std::vector
、std::pair
、std::map
など)に対しては、要素の数と格納されている各要素に対するシリアライズ/デシリアライズ処理を行います。
template <typename T, size_t N>
void Calculate(const std::array<T, N>& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
for (const auto& item : in_data)
{
this->Calculate(item, out_size);
}
}
template <typename T, size_t N>
void Serialize(const std::array<T, N>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(static_cast<uint64_t>(in_data.size()), offset, out_archive);
for (const auto& item : in_data)
{
this->Serialize(item, offset, out_archive);
}
}
template <typename T, size_t N>
void Deserialize(const Archive& in_archive, size_t& offset, std::array<T, N>& out_data)
{
uint64_t size = 0;
this->Deserialize(in_archive, offset, size);
for (uint64_t i = 0; i < size; i++)
{
T item{};
this->Deserialize(in_archive, offset, item);
out_data[i] = item;
}
}
template <typename T>
void Calculate(const std::vector<T>& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
for (const auto& item : in_data)
{
this->Calculate(item, out_size);
}
}
template <typename T>
void Serialize(const std::vector<T>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(static_cast<uint64_t>(in_data.size()), offset, out_archive);
for (const auto& item : in_data)
{
this->Serialize(item, offset, out_archive);
}
}
template <typename T>
void Deserialize(const Archive& in_archive, size_t& offset, std::vector<T>& out_data)
{
uint64_t size = 0;
this->Deserialize(in_archive, offset, size);
out_data.reserve(size);
for (uint64_t i = 0; i < size; i++)
{
T item{};
this->Deserialize(in_archive, offset, item);
out_data.push_back(item);
}
}
template <typename T1, typename T2>
void Calculate(const std::pair<T1, T2>& in_data, size_t& out_size)
{
this->Calculate(in_data.first, out_size);
this->Calculate(in_data.second, out_size);
}
template <typename T1, typename T2>
void Serialize(const std::pair<T1, T2>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(in_data.first, offset, out_archive);
this->Serialize(in_data.second, offset, out_archive);
}
template <typename T1, typename T2>
void Deserialize(const Archive& in_archive, size_t& offset, std::pair<T1, T2>& out_data)
{
T1 first{};
this->Deserialize(in_archive, offset, first);
T2 second{};
this->Deserialize(in_archive, offset, second);
out_data = std::make_pair<T1, T2>(first, second);
}
template <typename TKey, typename TValue>
void Calculate(const std::map<TKey, TValue>& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
for (const auto& item : in_data)
{
this->Calculate(item, out_size);
}
}
template <typename TKey, typename TValue>
void Serialize(const std::map<TKey, TValue>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(in_data.size(), offset, out_archive);
for (const auto& item : in_data)
{
this->Serialize(item, offset, out_archive);
}
}
template <typename TKey, typename TValue>
void Deserialize(const Archive& in_archive, size_t& offset, std::map<TKey, TValue>& out_data)
{
uint64_t size = 0;
this->Deserialize(in_archive, offset, size);
for (uint64_t i = 0; i < size; i++)
{
TKey key{};
this->Deserialize(in_archive, offset, key);
TValue value{};
this->Deserialize(in_archive, offset, value);
out_data.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(value));
}
}
例としてstd::array
に対するシリアライズ/デシリアライズ処理について説明します。
Calculate()
では、size()
で取得できる要素の数に対するデータサイズを加算し、その後各要素に対してデータサイズを算出するCalculate()
を呼び出します。
size()
取得できるデータサイズの型はsize_t
型ですが、size_t
型は環境により型のサイズが異なるため、uint64_t
型で加算しています。
Serialize()
では、size()
で取得した要素数をuint64_t
型にキャストしてSerialize()
を呼び出すことで要素数をシリアライズし、その後各要素に対してシリアライズを行うSerialize()
を呼び出します。
Deserialize()
では、最初に要素数をデシリアライズ(uint64_t
型としてDeserialize()
を呼び出し)し、要素数分Deserialize()
を呼び出して各要素をデシリアライズします。
std::vector
やstd::map
も同様に実装しています。
std::pair
はメンバのfirst
、second
に対してそれぞれCalculate()
、Serialize()
、Deserialize()
を行うようにしています。
文字列型
文字列型(std::string
)も基本的にはSTLのstd::vector
と同様の方法で実装していますが、文字列の場合は、終端にnull文字('\0'
)を追加するようにしています。
そのため、要素数にもnull文字分(char
型)のサイズを加算しています。
inline void Calculate(const string_t& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
uint64_t data_size = static_cast<uint64_t>(in_data.size() + sizeof(char));
out_size += data_size;
}
inline void Serialize(const string_t& in_data, size_t& offset, Archive& out_archive)
{
uint64_t data_size = static_cast<uint64_t>(in_data.size() + sizeof(char));
this->Serialize(data_size, offset, out_archive);
out_archive.Write(in_data, offset);
}
inline void Deserialize(const Archive& in_archive, size_t& offset, string_t& out_data)
{
uint64_t data_size = 0;
this->Deserialize(in_archive, offset, data_size);
in_archive.Read(out_data, offset, static_cast<size_t>(data_size));
}
クラス/構造体型
ここが今回肝となる部分の実装です。
任意のクラス/構造体のシリアライズ処理を行うためには、メンバ変数を列挙して各メンバに対してCalculate()
、Serialize()
、Deserialize()
を呼び出す必要があります。
C#などであれば、リフレクションによってメンバ変数を列挙することが可能ですが、C++ではそういった機能は実装されていないので、疑似的にメンバ変数を列挙する処理を実装する必要があります。
今回は、クラス/構造体型のメンバ変数をタプル(std::tuple
)型で列挙する関数を定義し、そのタプルに対してSwallowイディオムおよびIndex Tupleイディオムを用いて可変長テンプレートのテンプレートパラメータパックを展開する方法を用いています。
SwallowイディオムおよびIndex Tupleイディオムに関しては、下記のページで詳しく説明されています。
C++魔術回路構成法
template <typename TUPLE, size_t ...I>
void CalculateTupleImple(size_t& out_size, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Calculate(std::get<I>(t), out_size)), 0)...
};
}
template <typename TUPLE, size_t ...I>
void SerializeTupleImple(size_t& offset, Archive& out_archive, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Serialize(std::get<I>(t), offset, out_archive)), 0)...
};
}
template <typename TUPLE, size_t ...I>
void DeserializeTupleImple(const Archive& in_archive, size_t& offset, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Deserialize(in_archive, offset, std::get<I>(t))), 0)...
};
}
template <typename TUPLE>
void CalculateTuple(size_t& out_size, TUPLE&& t)
{
this->CalculateTupleImple(out_size, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template <typename TUPLE>
void SerializeTuple(size_t& offset, Archive& out_archive, TUPLE&& t)
{
this->SerializeTupleImple(offset, out_archive, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template <typename TUPLE>
void DeserializeTuple(const Archive& in_archive, size_t& offset, TUPLE&& t)
{
this->DeserializeTupleImple(in_archive, offset, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template<class T>
void Calculate(const T& in_data, size_t& out_size, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetMembersAsTuple(in_data);
this->CalculateTuple(out_size, tuple);
}
template <class T>
void Serialize(const T& in_data, size_t& offset, Archive& out_archive, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetMembersAsTuple(in_data);
this->SerializeTuple(offset, out_archive, tuple);
}
template <class T>
void Deserialize(const Archive& in_archive, size_t& offset, T& out_data, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetMembersAsTuple(out_data);
this->DeserializeTuple(in_archive, offset, tuple);
}
Calculate()
を例に説明します。
Calculate()
では、type_traits::DataTypeTraits<T>::GetMembersAsTuple()
を呼び出すことで、入力データのメンバをタプル(std::tuple
)型で取得し、CalculateTuple()
に渡しています。
CalculateTuple()
ではstd::forward
でタプルデータを渡し、またstd::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{}
でタプルの要素数分のインデックスシーケンスを生成して渡しています。
さらにCalculateTupleImple()
では、渡されたタプルと要素のインデックスにより指定インデックス位置のメンバ変数を取り出し、メンバの型に対応したCalculate()
を呼び出しています。
タプル要素インデックス(I
)は可変長テンプレートパラメータとなっており、SwallowイディオムおよびIndex Tupleイディオムによって順次展開されるため、すべてのタプルに含まれるすべてのメンバに対して順番にCalculate()
が呼ばれる仕組みになっています。
type_traits::DataTypeTraits<T>::GetMembersAsTuple()
の定義方法は後ほど使い方の項で説明します。
ソース全体は以下の通りです。
class BinarySerialization
{
public:
static BinarySerialization& GetInstance()
{
static BinarySerialization instance;
return instance;
}
private:
BinarySerialization()
: m_CurrentEndian(endian::GetEnvironmentEndian())
, m_TargetEndian(endian::EndianType::Network)
{
}
~BinarySerialization() = default;
public:
BinarySerialization(const BinarySerialization&) = delete;
BinarySerialization(BinarySerialization&&) = delete;
BinarySerialization& operator=(const BinarySerialization&) = delete;
BinarySerialization& operator=(BinarySerialization&&) = delete;
public:
template <typename T>
void CalculateSize(const T& in_data, size_t& out_size)
{
this->Calculate(in_data, out_size);
}
template <typename T>
void Serialize(const T& in_data, Archive& out_archive)
{
size_t size = 0;
this->CalculateSize(in_data, size);
out_archive.Reserve(size);
size_t offset = 0;
this->Serialize(in_data, offset, out_archive);
}
template <typename T>
void Deserialize(const Archive& in_archive, T& out_data)
{
size_t offset = 0;
this->Deserialize(in_archive, offset, out_data);
}
private:
template<typename T, type_traits::concept_t<std::is_arithmetic<T>::value> = nullptr>
void Calculate(const T& in_data, size_t& out_size)
{
out_size += sizeof(in_data);
}
template<typename T, type_traits::concept_t<std::is_arithmetic<T>::value> = nullptr>
void Serialize(const T& in_data, size_t& offset, Archive& out_archive)
{
std::array<byte_t, sizeof(T)> in_bytes{};
binary::BinaryConverter::Convert(this->m_CurrentEndian, this->m_TargetEndian, in_data, in_bytes);
out_archive.Write(in_bytes, offset);
}
template<typename T, type_traits::concept_t<std::is_arithmetic<T>::value> = nullptr>
void Deserialize(const Archive& in_archive, size_t& offset, T& out_data)
{
std::array<byte_t, sizeof(T)> out_bytes{};
in_archive.Read(out_bytes, offset);
binary::BinaryConverter::Convert(this->m_CurrentEndian, this->m_TargetEndian, out_bytes, out_data);
}
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Calculate(const T& in_data, size_t& out_size)
{
this->Calculate(type_traits::underlying_cast(in_data), out_size);
}
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Serialize(const T& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(type_traits::underlying_cast(in_data), offset, out_archive);
}
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Deserialize(const Archive& in_archive, size_t& offset, T& out_data)
{
type_traits::underlying_type_t<T> data{};
this->Deserialize(in_archive, offset, data);
out_data = static_cast<T>(data);
}
template <typename T, size_t N>
void Calculate(const std::array<T, N>& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
for (const auto& item : in_data)
{
this->Calculate(item, out_size);
}
}
template <typename T, size_t N>
void Serialize(const std::array<T, N>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(static_cast<uint64_t>(in_data.size()), offset, out_archive);
for (const auto& item : in_data)
{
this->Serialize(item, offset, out_archive);
}
}
template <typename T, size_t N>
void Deserialize(const Archive& in_archive, size_t& offset, std::array<T, N>& out_data)
{
uint64_t size = 0;
this->Deserialize(in_archive, offset, size);
for (uint64_t i = 0; i < size; i++)
{
T item{};
this->Deserialize(in_archive, offset, item);
out_data[i] = item;
}
}
template <typename T>
void Calculate(const std::vector<T>& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
for (const auto& item : in_data)
{
this->Calculate(item, out_size);
}
}
template <typename T>
void Serialize(const std::vector<T>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(static_cast<uint64_t>(in_data.size()), offset, out_archive);
for (const auto& item : in_data)
{
this->Serialize(item, offset, out_archive);
}
}
template <typename T>
void Deserialize(const Archive& in_archive, size_t& offset, std::vector<T>& out_data)
{
uint64_t size = 0;
this->Deserialize(in_archive, offset, size);
out_data.reserve(size);
for (uint64_t i = 0; i < size; i++)
{
T item{};
this->Deserialize(in_archive, offset, item);
out_data.push_back(item);
}
}
template <typename T1, typename T2>
void Calculate(const std::pair<T1, T2>& in_data, size_t& out_size)
{
this->Calculate(in_data.first, out_size);
this->Calculate(in_data.second, out_size);
}
template <typename T1, typename T2>
void Serialize(const std::pair<T1, T2>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(in_data.first, offset, out_archive);
this->Serialize(in_data.second, offset, out_archive);
}
template <typename T1, typename T2>
void Deserialize(const Archive& in_archive, size_t& offset, std::pair<T1, T2>& out_data)
{
T1 first{};
this->Deserialize(in_archive, offset, first);
T2 second{};
this->Deserialize(in_archive, offset, second);
out_data = std::make_pair<T1, T2>(first, second);
}
template <typename TKey, typename TValue>
void Calculate(const std::map<TKey, TValue>& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
for (const auto& item : in_data)
{
this->Calculate(item, out_size);
}
}
template <typename TKey, typename TValue>
void Serialize(const std::map<TKey, TValue>& in_data, size_t& offset, Archive& out_archive)
{
this->Serialize(in_data.size(), offset, out_archive);
for (const auto& item : in_data)
{
this->Serialize(item, offset, out_archive);
}
}
template <typename TKey, typename TValue>
void Deserialize(const Archive& in_archive, size_t& offset, std::map<TKey, TValue>& out_data)
{
uint64_t size = 0;
this->Deserialize(in_archive, offset, size);
for (uint64_t i = 0; i < size; i++)
{
TKey key{};
this->Deserialize(in_archive, offset, key);
TValue value{};
this->Deserialize(in_archive, offset, value);
out_data.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(value));
}
}
template <typename TUPLE, size_t ...I>
void CalculateTupleImple(size_t& out_size, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Calculate(std::get<I>(t), out_size)), 0)...
};
}
template <typename TUPLE, size_t ...I>
void SerializeTupleImple(size_t& offset, Archive& out_archive, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Serialize(std::get<I>(t), offset, out_archive)), 0)...
};
}
template <typename TUPLE, size_t ...I>
void DeserializeTupleImple(const Archive& in_archive, size_t& offset, TUPLE&& t, std::index_sequence<I...>)
{
using swallow = std::initializer_list<int>;
(void)swallow
{
(void(this->Deserialize(in_archive, offset, std::get<I>(t))), 0)...
};
}
template <typename TUPLE>
void CalculateTuple(size_t& out_size, TUPLE&& t)
{
this->CalculateTupleImple(out_size, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template <typename TUPLE>
void SerializeTuple(size_t& offset, Archive& out_archive, TUPLE&& t)
{
this->SerializeTupleImple(offset, out_archive, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template <typename TUPLE>
void DeserializeTuple(const Archive& in_archive, size_t& offset, TUPLE&& t)
{
this->DeserializeTupleImple(in_archive, offset, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}
template<class T>
void Calculate(const T& in_data, size_t& out_size, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetMembersAsTuple(in_data);
this->CalculateTuple(out_size, tuple);
}
template <class T>
void Serialize(const T& in_data, size_t& offset, Archive& out_archive, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetMembersAsTuple(in_data);
this->SerializeTuple(offset, out_archive, tuple);
}
template <class T>
void Deserialize(const Archive& in_archive, size_t& offset, T& out_data, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
auto tuple = type_traits::DataTypeTraits<T>::GetMembersAsTuple(out_data);
this->DeserializeTuple(in_archive, offset, tuple);
}
inline void Calculate(const string_t& in_data, size_t& out_size)
{
out_size += sizeof(uint64_t);
uint64_t data_size = static_cast<uint64_t>(in_data.size() + sizeof(char));
out_size += data_size;
}
inline void Serialize(const string_t& in_data, size_t& offset, Archive& out_archive)
{
uint64_t data_size = static_cast<uint64_t>(in_data.size() + sizeof(char));
this->Serialize(data_size, offset, out_archive);
out_archive.Write(in_data, offset);
}
inline void Deserialize(const Archive& in_archive, size_t& offset, string_t& out_data)
{
uint64_t data_size = 0;
this->Deserialize(in_archive, offset, data_size);
in_archive.Read(out_data, offset, static_cast<size_t>(data_size));
}
private:
endian::EndianType m_CurrentEndian;
endian::EndianType m_TargetEndian;
};
あとがき
今回はバイナリ形式でのシリアライズ処理を実装しました。
次回は、テキスト形式でのシリアライズ処理の実装について説明します。