web-dev-qa-db-ja.com

C ++でオブジェクトをどのようにシリアル化しますか?

ソケット接続を介してシリアル化して送信する必要があるオブジェクトの小さな階層があります。オブジェクトをシリアル化してから、オブジェクトの種類に基づいて逆シリアル化する必要があります。 C++でこれを行う簡単な方法はありますか(Javaにあるように)?

C++シリアル化のオンラインコードサンプルまたはチュートリアルはありますか?

編集:明確にするために、オブジェクトをバイト配列に変換してからオブジェクトに戻す方法を探しています。ソケット伝送を処理できます。

77
Bill the Lizard

シリアライゼーションについて言えば、 boost serialization API が思い浮かびます。シリアル化されたデータをネット経由で送信するには、Berkeleyソケットまたは asio library を使用します。

編集:
オブジェクトをバイト配列にシリアル化する場合は、次の方法でブーストシリアライザーを使用できます(チュートリアルサイトから入手)。

#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
    }

逆シリアル化も同様の方法で機能します。

また、ポインターの直列化(房などの複雑なデータ構造は問題ありません)、派生クラスを処理できるメカニズムがあり、バイナリまたはテキストの直列化を選択できます。さらに、すべてのSTLコンテナがデフォルトでサポートされています。

51
newgre

単純な型を扱う場合、次のことができます。

object o;
socket.write(&o, sizeof(o));

概念実証または最初のドラフトとしては問題ないので、チームの他のメンバーは他の部分で作業を続けることができます。

しかし、遅かれ早かれ、通常遅かれ早かれ、これはあなたを傷つけるでしょう!

次の問題が発生します。

  • 仮想ポインタテーブルが破損します。
  • (データ/メンバー/関数への)ポインターが破損します。
  • 異なるマシンでのパディング/アライメントの違い。
  • ビッグ/リトルエンディアンのバイト順序の問題。
  • Float/doubleの実装のバリエーション。

(さらに、受信側で開梱する内容を知る必要があります。)

これを改善するには、クラスごとに独自のマーシャリング/アンマーシャリングメソッドを開発します。 (理想的には仮想であるため、サブクラスで拡張できます。)いくつかの単純なマクロを使用すると、さまざまな基本タイプをビッグ/リトルエンディアン中立の順序で非常に迅速に記述できます。

しかし、この種の単調な作業は、 boostのシリアル化ライブラリ を介して処理する方がはるかに優れており、より簡単です。

13
Mr.Ree

シリアル化とは、オブジェクトをバイナリデータに変換することです。逆シリアル化とは、データからオブジェクトを再作成することを意味します。

シリアル化するとき、バイトを_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);
_

浮動小数点データのシリアル化

私の知る限り、IEEE 754はここで独占しています。私は、フロートに別のものを使用する主流のアーキテクチャを知りません。異なる可能性がある唯一のものは、バイト順です。リトルエンディアンを使用するアーキテクチャもあれば、ビッグエンディアンのバイト順を使用するアーキテクチャもあります。これは、受信側のバイトを大きくする順序に注意する必要があることを意味します。別の違いは、非正規値、無限値、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。

基本的にここで行ったことは、ポインタを配列インデックスに置き換えたものです。これで、シリアル化するときに、この配列インデックスを通常どおりシリアル化できます。オブジェクトが宛先システムのメモリ内のどこにあるかを心配する必要はありません。それらが同じオブジェクトプールを持っていることを確認してください。

そのため、オブジェクトプールをシリアル化する必要があります。しかし、どれですか?オブジェクトグラフをシリアル化すると、オブジェクトだけをシリアル化するのではなく、システム全体をシリアル化することになります。これは、システムのシリアル化がシステムの一部から始まってはならないことを意味します。これらのオブジェクトは、システムの残りの部分を心配する必要はありません。配列インデックスをシリアル化するだけで十分です。システムのシリアル化を調整し、関連するオブジェクトプールを調べてすべてをシリアル化するシステムシリアライザールーチンが必要です。

受信側では、すべての配列とその中のオブジェクトが逆シリアル化され、目的のオブジェクトグラフが再作成されます。

関数ポインターのシリアル化

オブジェクトにポインターを格納しないでください。これらの関数へのポインターを含む静的配列を持ち、オブジェクトにインデックスを保存します。

両方のプログラムはこのテーブルをそれらのシェルフにコンパイルしているので、インデックスだけを使用しても機能します。

多相型のシリアル化

シリアル化可能な型ではポインターを避け、代わりに配列インデックスを使用する必要があると述べたため、ポリモーフィズムはポインターを必要とするため機能しません。

型タグと共用体でこれを回避する必要があります。

バージョニング

上記のすべての上に。ソフトウェアの異なるバージョンを相互運用することができます。

この場合、各オブジェクトは、バージョンを示すために、シリアル化の最初にバージョン番号を書き込む必要があります。

反対側のオブジェクトをロードするとき、新しいオブジェクトは古い表現を処理できるかもしれませんが、古いものは新しいものを処理できないため、これに関する例外をスローする必要があります。

何かが変わるたびに、バージョン番号を上げる必要があります。


したがって、これをまとめるために、シリアル化は複雑になる可能性があります。しかし幸いなことに、プログラムのすべてをシリアル化する必要はありません。ほとんどの場合、プロトコルメッセージのみがシリアル化されます。これは単純な古い構造体です。そのため、上記で説明した複雑なトリックはあまり必要ありません。

1
Calmarius