ソケット接続を介してシリアル化および送信する必要があるオブジェクトの小さな階層があります。オブジェクトをシリアル化してから、タイプに基づいて逆シリアル化する必要があります。これをC ++で行う簡単な方法はありますか(Javaの場合と同様)?
C ++シリアル化のオンラインコードサンプルまたはチュートリアルはありますか?
編集:明確にするために、私はオブジェクトをバイトの配列に変換してからオブジェクトに戻す方法を探しています。ソケット送信も対応できます。
ソケット接続を介してシリアル化および送信する必要があるオブジェクトの小さな階層があります。オブジェクトをシリアル化してから、タイプに基づいて逆シリアル化する必要があります。これをC ++で行う簡単な方法はありますか(Javaの場合と同様)?
C ++シリアル化のオンラインコードサンプルまたはチュートリアルはありますか?
編集:明確にするために、私はオブジェクトをバイトの配列に変換してからオブジェクトに戻す方法を探しています。ソケット送信も対応できます。
回答:
シリアル化について話すと、ブーストシリアル化APIが頭に浮かびます。シリアル化されたデータをネット経由で送信する場合は、バークレーソケットまたはasioライブラリを使用します。
編集:
オブジェクトをバイト配列にシリアル化する場合は、次の方法でブーストシリアライザーを使用できます(チュートリアルサイトから取得)。
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};
実際のシリアル化は非常に簡単です。
#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);
// create class instance
const gps_position g(35, 59, 24.567f);
// save data to archive
{
boost::archive::binary_oarchive oa(ofs);
// write class instance to archive
oa << g;
// archive and stream closed when destructors are called
}
デシリアライズは同様の方法で機能します。
ポインタのシリアル化(tressなどの複雑なデータ構造は問題ありません)、派生クラスを処理できるメカニズムもあり、バイナリシリアル化とテキストシリアル化のどちらかを選択できます。さらに、すべてのSTLコンテナがすぐにサポートされます。
場合によっては、単純な型を扱うときに、次のことができます。
object o;
socket.write(&o, sizeof(o));
これは、概念実証または最初のドラフトとしては問題ないため、チームの他のメンバーは他の部分で作業を続けることができます。
しかし遅かれ早かれ、通常はもっと早く、これはあなたを傷つけるでしょう!
次の問題が発生します。
(さらに、受信側で何に開梱するかを知る必要があります。)
クラスごとに独自のマーシャリング/アンマーシャリングメソッドを開発することで、これを改善できます。(理想的には仮想なので、サブクラスで拡張できます。)いくつかの単純なマクロを使用すると、さまざまな基本型をビッグ/リトルエンディアンニュートラルの順序で非常にすばやく書き出すことができます。
しかし、そのようなうなり声の作業は、boostのシリアル化ライブラリを介してはるかに優れており、より簡単に処理できます。
オブジェクトをシリアル化するために使用できる一般的なパターンがあります。基本的なプリミティブは、イテレータから読み書きできる次の2つの関数です。
template <class OutputCharIterator>
void putByte(char byte, OutputCharIterator &&it)
{
*it = byte;
++it;
}
template <class InputCharIterator>
char getByte(InputCharIterator &&it, InputCharIterator &&end)
{
if (it == end)
{
throw std::runtime_error{"Unexpected end of stream."};
}
char byte = *it;
++it;
return byte;
}
次に、シリアル化および逆シリアル化関数は次のパターンに従います。
template <class OutputCharIterator>
void serialize(const YourType &obj, OutputCharIterator &&it)
{
// Call putbyte or other serialize overloads.
}
template <class InputCharIterator>
void deserialize(YourType &obj, InputCharIterator &&it, InputCharIterator &&end)
{
// Call getByte or other deserialize overloads.
}
クラスの場合、フレンド関数パターンを使用して、ADLを使用してオーバーロードを検出できるようにすることができます。
class Foo
{
int internal1, internal2;
// So it can be found using ADL and it accesses private parts.
template <class OutputCharIterator>
friend void serialize(const Foo &obj, OutputCharIterator &&it)
{
// Call putByte or other serialize overloads.
}
// Deserialize similar.
};
プログラムでは、次のようなファイルにシリアル化してオブジェクトを作成できます。
std::ofstream file("savestate.bin");
serialize(yourObject, std::ostreambuf_iterator<char>(file));
次に読んでください:
std::ifstream file("savestate.bin");
deserialize(yourObject, std::istreamBuf_iterator<char>(file), std::istreamBuf_iterator<char>());
ここでの私の古い答え:
シリアル化とは、オブジェクトをバイナリデータに変換することを意味します。一方、逆シリアル化とは、データからオブジェクトを再作成することを意味します。
シリアル化するときは、バイトをuint8_t
ベクトルにプッシュします。シリアル化を解除すると、uint8_t
ベクトルからバイトを読み取ることになります。
ものをシリアル化するときに採用できるパターンは確かにあります。
シリアル化可能な各クラスにはserialize(std::vector<uint8_t> &binaryData)
、提供されたベクトルにバイナリ表現を書き込む、または同様の署名付き関数が必要です。次に、この関数は、このベクトルをメンバーのシリアル化関数に渡して、メンバーが自分のものを書き込むことができるようにします。
データ表現はアーキテクチャによって異なる可能性があるためです。データを表現する方法を見つける必要があります。
基本から始めましょう:
バイトをリトルエンディアンの順序で書き込むだけです。または、サイズが重要な場合は、varint表現を使用します。
リトルエンディアン順のシリアル化:
data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);
リトルエンディアン順からの逆シリアル化:
integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
私の知る限り、IEEE754はここで独占しています。フロートに他の何かを使用する主流のアーキテクチャを私は知りません。異なる可能性があるのはバイト順序だけです。リトルエンディアンを使用するアーキテクチャもあれば、ビッグエンディアンのバイトオーダーを使用するアーキテクチャもあります。これは、受信側でバイトを大きくする順序に注意する必要があることを意味します。もう1つの違いは、非正規化数と無限大およびNAN値の処理です。ただし、これらの値を回避する限り、問題はありません。
シリアル化:
uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...
デシリアライズはそれを逆方向に実行しています。アーキテクチャのバイト順序に注意してください。
まず、エンコーディングについて合意する必要があります。UTF-8が一般的です。次に、長さのプレフィックスとして保存します。最初に、前述の方法を使用して文字列の長さを保存し、次に文字列をバイトごとに書き込みます。
それらは文字列と同じです。最初に配列のサイズを表す整数をシリアル化し、次にその中の各オブジェクトをシリアル化します。
前に言ったserialize
ように、ベクターにコンテンツを追加するメソッドが必要です。オブジェクトのシリアル化を解除するには、バイトストリームを受け取るコンストラクターが必要です。それは可能性がありますistream
が、最も単純なケースでは、単なる参照uint8_t
ポインターである可能性があります。コンストラクターは、ストリームから必要なバイトを読み取り、オブジェクトにフィールドを設定します。システムが適切に設計され、オブジェクトフィールドの順序でフィールドをシリアル化する場合は、ストリームをイニシャライザリスト内のフィールドのコンストラクタに渡し、正しい順序で逆シリアル化することができます。
まず、これらのオブジェクトが本当にシリアル化したいものであるかどうかを確認する必要があります。これらのオブジェクトのインスタンスが宛先に存在する場合は、それらをシリアル化する必要はありません。
これで、ポインタが指すオブジェクトをシリアル化する必要があることがわかりました。ポインタが有効であるという問題は、それらを使用するプログラム内でのみ有効です。ポインタをシリアル化することはできません。オブジェクトでの使用を停止する必要があります。代わりに、オブジェクトプールを作成します。このオブジェクトプールは基本的に「ボックス」を含む動的配列です。これらのボックスには参照カウントがあります。ゼロ以外の参照カウントはライブオブジェクトを示し、ゼロは空のスロットを示します。次に、オブジェクトへのポインターではなく、配列内のインデックスを格納するshared_ptrに類似したスマートポインターを作成します。また、nullポインタを示すインデックスについても同意する必要があります。-1。
基本的に、ここで行ったことは、ポインターを配列インデックスに置き換えたことです。これで、シリアル化するときに、通常どおりこの配列インデックスをシリアル化できます。オブジェクトが宛先システムのメモリのどこにあるかを心配する必要はありません。それらにも同じオブジェクトプールがあることを確認してください。
したがって、オブジェクトプールをシリアル化する必要があります。しかし、どれですか?オブジェクトグラフをシリアル化する場合、オブジェクトだけをシリアル化するのではなく、システム全体をシリアル化することになります。これは、システムのシリアル化がシステムの一部から開始されるべきではないことを意味します。これらのオブジェクトは、システムの残りの部分について心配する必要はありません。配列インデックスをシリアル化するだけで済みます。システムのシリアル化を調整し、関連するオブジェクトプールをウォークスルーして、それらすべてをシリアル化するシステムシリアライザールーチンが必要です。
受信側では、すべての配列とその中のオブジェクトが逆シリアル化され、目的のオブジェクトグラフが再作成されます。
オブジェクトにポインタを格納しないでください。これらの関数へのポインタを含む静的配列を用意し、オブジェクトにインデックスを格納します。
どちらのプログラムでもこのテーブルがシェルフにコンパイルされているため、インデックスのみを使用すると機能するはずです。
シリアライズ可能な型のポインターは避け、代わりに配列インデックスを使用する必要があると述べたので、ポリモーフィズムはポインターを必要とするため、機能しません。
タイプタグとユニオンを使用してこれを回避する必要があります。
上記のすべてに加えて。異なるバージョンのソフトウェアを相互運用したい場合があります。
この場合、各オブジェクトは、バージョンを示すために、シリアル化の開始時にバージョン番号を書き込む必要があります。
反対側のオブジェクトをロードすると、新しいオブジェクトは古い表現を処理できる可能性がありますが、古いオブジェクトは新しい表現を処理できないため、これについて例外をスローする必要があります。
何かが変わるたびに、バージョン番号を上げる必要があります。
したがって、これをまとめると、シリアル化は複雑になる可能性があります。しかし幸いなことに、プログラム内のすべてをシリアル化する必要はありません。ほとんどの場合、プロトコルメッセージのみがシリアル化されます。これは、多くの場合、単純な古い構造体です。したがって、上記の複雑なトリックはあまり必要ありません。
学習として、単純なC ++ 11シリアライザーを作成しました。私は他のより重い製品をいろいろ試しましたが、それがうまくいかなかったり、最新のg ++でコンパイルできなかったときに実際に理解できるものが欲しかったです(これはCerealで起こりました;本当に素晴らしいライブラリですが複雑で、私は理解できませんでしたコンパイラがアップグレード時にスローしたエラー。)とにかく、それはヘッダーのみであり、PODタイプ、コンテナ、マップなどを処理します...バージョン管理は行われず、保存されたのと同じアーチからファイルのみがロードされます。
https://github.com/goblinhack/simple-c-plus-plus-serializer
使用例:
#include "c_plus_plus_serializer.h"
static void serialize (std::ofstream out)
{
char a = 42;
unsigned short b = 65535;
int c = 123456;
float d = std::numeric_limits<float>::max();
double e = std::numeric_limits<double>::max();
std::string f("hello");
out << bits(a) << bits(b) << bits(c) << bits(d);
out << bits(e) << bits(f);
}
static void deserialize (std::ifstream in)
{
char a;
unsigned short b;
int c;
float d;
double e;
std::string f;
in >> bits(a) >> bits(b) >> bits(c) >> bits(d);
in >> bits(e) >> bits(f);
}