7

シリアライゼーションとは何か、何をするのか理解するのに苦労しています。

私の問題を単純化しましょう。私struct infoの c/c++ プログラムには があり、このstructデータをファイルに保存しsave.binたり、ソケット経由で別のコンピューターに送信したりできます。

struct info {
    std::string name;
    int age;
};

void write_to_file()
{
    info a = {"Steve", 10};
    ofstream ofs("save.bin", ofstream::binary);
    ofs.write((char *) &a, sizeof(a));   // am I doing it right?
    ofs.close();
}

void write_to_sock()
{
    // I don't know about socket api, but I assume write **a** to socket is similar to file, isn't it?
}

write_to_filestruct infoオブジェクトをディスクに保存するだけaで、このデータが永続化されますよね? ソケットへの書き込みはほとんど同じですよね?

上記のコードでは、 data serializationを使用したとは思いませんが、とにかくデータaは永続化されていますよね?save.bin

質問

  1. では連載のポイントは?ここで必要ですか?はいの場合、どのように使用すればよいですか?

  2. 私は常に、どのような種類のファイルもメモリ内.txt/.csv/.exe/...のビットであると考えています。01つまり、それらは自然にバイナリ表現を持っているので、これらのファイルをソケット経由で直接送信することはできませんか?

コード例は高く評価されています。

4

6 に答える 6

6

とにかく、データはsave.binで永続化されますよね?

いいえ!構造体にはstd::string. 正確な実装(およびキャストで取得するバイナリデータchar*は標準では定義されていませんが、実際の文字列データは常にヒープ割り当てされたクラスフレームの外側に残るため、そのデータを簡単に保存することはできません. 適切にシリアル化を行うと、文字列データはクラスの残りの部分にも書き込まれるため、ファイルから読み返すことができます. そのためにシリアル化が必要です.

方法: 文字列を何らかの方法でエンコードする必要があります。最も簡単な方法は、最初に文字列の長さを書き、次に文字列自体を書き出すことです。ファイルを読み戻すときは、まず長さを読み返し、次にそのバイト数を新しい文字列オブジェクトに読み込みます。

私はいつも、どんな種類のファイル、.txt/.csv/.exe/... もメモリ内の 01 のビットだと思っています。

はい、しかし問題は、どのビットがデータ構造のどの部分を表すかが普遍的に定義されていないことです。特に、リトルエンディアンとビッグエンディアンのアーキテクチャがあり、ビットを「逆に」保存します。一致しないアーキテクチャで記述されたファイルを素朴に読み取ると、明らかにゴミが発生します。

于 2012-08-17T07:16:21.967 に答える
5

メモリ内のバイナリ イメージを書き留めるだけでもシリアル化の 1 つの形式であり、些細なケースでは機能します。ただし、一般に、メモリをダンプするだけでは考慮されないいくつかの問題を解決する必要があります。

1.ポインター

もちろん、データにポインターが含まれている場合、ポインターが指しているメモリアドレスは、プログラムが終了して再起動すると意味がなくなるため、後でロードをダンプすることはできません。多くのオブジェクトには「隠された」ポインターがあります...たとえば、std::vectorをメモリにダンプし、後で正しく再ロードする方法はありません...sizeofstd::vector含まれている要素のサイズが明らかに含まれていないため、を含む構造はstd::vector単にダンプできませんとリロードしました。同じことがstd::string他のすべてのstdコンテナにも当てはまります。

2.携帯性

C および C++ の構造体とクラスは、メモリ内で占有するバイト数に関して定義されていません。つまり、移植性がありません。これは、異なるコンパイラ、異なるコンパイラ バージョン、または同じバージョンでも異なるコンパイル オプションを使用すると、メモリ内の構造レイアウトが同じではないコードが生成される可能性があることを意味します。

同じプログラムでデータを保存してリロードするだけのシリアライゼーションが必要で、そのデータが長く存続するはずがない場合は、実際にメモリダンプを使用できます。ただし、構造をダンプするだけで何百万ものドキュメントが保存されていることを考えてみてください。新しいコンパイラ バージョン (新しい OS バージョンでのみサポートされているため、強制的に使用する必要があります) のレイアウトが異なり、それらのドキュメントを読み込めなくなります。 .

同一システムの移植性の問題に加えて、単一の整数であっても、異なるシステムでは異なるメモリ内表現を持つ可能性があることにも注意してください。大きくても小さくてもかまいません。バイト順が異なる場合があります。メモリダンプを使用するだけでは、保存されたものを別のシステムでロードすることはできません。単一の整数でさえありません。

3. バージョン管理

保存するデータの寿命が長い場合、プログラムが進化するにつれて構造を変更する可能性が非常に高くなります。たとえば、新しいフィールドを追加したり、未使用のフィールドを削除したり、一般的な構造を変更したりします (たとえば、ベクトルを連結リストに)。

あなたのフォーマットが現在のデータ構造の単なるメモリイメージである場合、たとえばオブジェクトにcolorフィールドを後で追加しpolygonたり、プログラムが古いドキュメントをデフォルトの色値として使用された色と仮定してロードできるようにしたりするのはかなり難しいでしょう.以前のバージョン。

古いドキュメントをロードできる古いコードと新しいドキュメントを保存できる新しいコードがあるため、変換プログラムを作成することさえ困難ですが、2 つを単純に「マージ」して、古いドキュメントをロードして新しいドキュメントを保存するプログラムを取得することはできません (つまり、両方とも)。プログラムのソース コードにはpolygon構造がありますが、フィールドが異なります。

于 2012-08-17T07:25:09.963 に答える
3

シリアル化は多くのことを行います。永続性(プログラムを終了してからプログラムに戻って同じデータを取得できる)、およびプロセスとマシン間の通信をサポートします。これは基本的に、内部データをバイトのシーケンスに変換することを意味します。有用であるためには、逆シリアル化もサポートする必要があります。つまり、バイトのシーケンスをデータに変換し直します。

これを行うときは、プログラムの内部では、データは単なるバイトのシーケンスではないことを理解することが重要です。形式と構造がありますdouble。たとえば、aの表現方法は、マシンごとに異なります。のようなより複雑なオブジェクトはstd::string、連続したメモリにもありません。したがって、シリアル化するときに最初に行う必要があるのは、各タイプがバイトのシーケンスとしてどのように表されるかを定義することです。別のプログラムと通信している場合は、両方のプログラムがこのシリアル形式に同意する必要があります。自分でデータを再読み込みできるようにするためだけであれば、任意の形式を使用できます(ただし、ドキュメントを簡素化するためだけに、XDRなどの事前定義された標準形式を使用することをお勧めします)。

できないのは、オブジェクトの画像をメモリにダンプすることだけです。のような複雑なオブジェクトstd::stringにはポインタが含まれ、これらのポインタは別のプロセスでは無意味になります。また、のような単純な型の表現でさえ、double時間の経過とともに変化する可能性があります。(32ビットから64ビットへの移行により、longほとんどのシステムでサイズが変更されました。)フォーマットを定義してから、使用しているデータからバイトごとに生成する必要があります。たとえば、XDRを作成するには、次のようなものを使用します。

typedef std::vector<char> Buffer;

void
writeUInt( Buffer& dest, unsigned value )
{
    dest.push_back( (value >> 24) & 0xFF );
    dest.push_back( (value >> 16) & 0xFF );
    dest.push_back( (value >>  8) & 0xFF );
    dest.push_back( (value      ) & 0xFF );
}

void
writeInt( Buffer& dest, int value )
{
    writeUInt( dest, static_cast<unsigned>( value ) );
}

void
writeString( Buffer& dest, std::string const& value)
{
    assert( value.size() <= 0xFFFFFFFF );
    writeInt( dest, value.size() )
    std::copy( value.begin(), value.end(), std::back_inserter( dest ) );
    while ( dest.size() % 4 != 0 ) {
        dest.push_back( '\0' );
    }
}
于 2012-08-17T10:02:32.617 に答える
3

あなたはゲームをしています。非常にハードモード。あなたは最後のレベルに到達します。あなたは幸せだ。2日間のノンストッププレイは成果を上げています。プロットはまもなく終了します。あなたは邪悪な首謀者の動機、あなたがどのようにしてヒーローになったのかを見つけ、その最後のドアの後ろで待っている人気のある壮大なアーティファクトを収集します。そして、あなたは一度再起動する必要なしにここに着きました。

舞台裏には、次のようなゲームオブジェクトがあります。

class GameState
{
   int level;
}

そしてレベルは25です。

これまでのところ、ゲームは本当に楽しかったですが、最後のボスがあなたを殺した場合に備えて、最初からやり直したくはありません。したがって、直感的に、を押しCtrl+Sます。しかし、待ってください。エラーが発生します。

Sorry, saving is disabled.

何?だから私は死んだ場合に備えて最初からやり直す必要がありますか?どうすればいいの。

ドラムロール

開発者は、素晴らしいとはいえ(彼らは、2日間連続してあなたを夢中にさせることができましたよね?)、シリアル化を実装しませんでした。

ゲームを再起動すると、メモリのクリーンアップが行われます。その最も重要なオブジェクト、つまりメンバーをGameState増やすために2日間費やしたオブジェクトは、破棄されます。level25

どうすればこれを修正できますか?ゲームを閉じると、OSによってメモリが再利用されます。どこに保管できますか?外部サーバー上?(ソケット)ディスク上?(ファイルへの書き込み)

さて、なぜですか。

class GameState
{
    int level;
    void save(const std::string& fileName)
    { /* write level to file */ }
    void load(const std::string& fileName)
    { /* read game state from file */ }
};

を押すCtrl+sと、GameStateオブジェクトがファイルに保存されます。

そして、奇跡的に、ゲームをロードすると、GameStateオブジェクトがそのファイルから読み取られます。最後のボスに戻るのに2日を費やす必要はもうありません。あなたはすでにそこにいます。

本当の答え:

技術的には、シリアル化機能を作成するのはかなり困難です。サードパーティを使用することをお勧めします。Googleプロトコルバッファは、クロスプラットフォーム、さらにはクロス言語のシリアル化を提供します。他にもたくさんあります。

1.では、シリアル化のポイントは何ですか?ここで必要ですか?はいの場合、どのように使用すればよいですか?

上で説明したように、実行間、またはプロセス間(おそらく異なるマシン上)の状態を格納します。必要かどうかは、状態を保存して後で再ロードする必要があるかどうかによって異なります。

2.あらゆる種類のファイル、.txt / .csv / .exe / ...は、メモリ内の01のビットであると常に考えています。つまり、これらのファイルは自然にバイナリ表現であるため、これらのファイルを単にソケット経由で送信することはできません。直接?

彼らです。.exeただし、新しいゲームをプレイするたびに変更する必要はありません。

于 2012-08-17T07:19:27.913 に答える
3

文字列が正しく保存されません。異なるマシンを使用している場合、整数の表現が異なる可能性があります。たとえば、異なるプログラミング言語では文字列の表現が同じではありません。

ただし、メンバーへのポインターがある場合は、ポイントされたメンバーではなくポインター アドレスを保存します。つまり、ファイルからそのデータを再度取得する方法がありません。構造を変更する必要がある場合はどうしますか? データを使用するすべてのソフトウェアを変更する必要があります。

はい、ソケット経由でファイルを送信できますが、ファイルの名前とファイルの最後に到達したことを確認するために、ある種のプロトコルが必要になります。

于 2012-08-17T07:17:53.743 に答える
1

ビッグ エディアンまたはリトル エンディアンとは別に、そのコンパイラを使用して、そのプログラムの特定の構造に対してデータがどのようにパックされるかという問題があります。構造体全体を保存する場合は、ポインターを使用できません。必要に応じて十分な大きさの文字バッファーに置き換える必要があります。他のマシンが同じアーキテクチャになる場合、#pragma pack(1) を使用すると、構造体のフィールド間にギャップがなくなり、データがシリアル化されたかのように表示されるようになります。ただし、文字列のサイズ接頭辞はありません。データを読み取る他のプログラムがまったく同じ構造に対してまったく同じ設定を持っていることが確実な場合は、#pragma pack(1) をスキップできます。それ以外では、データは一致しません。

最初にメモリにシリアル化すると、シリアル化プロセスを高速化できます。これは通常、ほとんどの型に対して 1 つのバッファー クラスと 1 つのテンプレート化された関数で実現できます。

template<typename T>
buffer& operator<<(T data)
{
    *(T*)buf = data;
    buf += sizeof(T);
}

明らかに、文字列やより大きなデータ型に特化したものが必要になります。大きな構造体には memcpy を使用し、データへのポインターを渡すことができます。文字列の場合、前述のように長さのプレフィックスを付ける必要があります。

ただし、深刻なシリアライゼーションが必要な場合は、考慮すべきことが他にもたくさんあります。

于 2012-08-17T09:24:13.783 に答える