8

私はiostreamのバイナリバージョンを書いています。基本的に、バイナリ ファイルを書き込むことができますが、ファイルの形式を細かく制御できます。使用例:

my_file << binary::u32le << my_int << binary::u16le << my_string;

my_int を符号なし 32 ビット整数として書き込み、my_string を長さのプレフィックス付き文字列 (プレフィックスは u16le) として書き込みます。ファイルを読み戻すには、矢印を反転させます。よく働く。しかし、私はデザインに問題があり、それについてはまだ迷っています。だから、SOに尋ねる時が来ました。(現時点では、8 ビット バイト、2 の補数の整数、IEEE 浮動小数点数など、いくつかの仮定を立てています。)

内部では、iostream は streambuf を使用します。これは実に素晴らしい設計です。iostreams は ' int' をテキストにシリアライズするコードを作成し、基になる streambuf に残りを処理させます。したがって、cout、fstream、stringstream などを取得します。これらはすべて、iostream と streambuf の両方で、通常は char でテンプレート化されますが、wchar としてテンプレート化されることもあります。ただし、私のデータはバイト ストリームであり、' unsigned char' で表すのが最適です。

私の最初の試みは、に基づいてクラスをテンプレート化することでしたunsigned charstd::basic_stringテンプレートで十分ですが、そうでstreambufはありません。という名前のクラスでいくつかの問題に遭遇しましたが、これはテーマcodecvtに従うことができませんでした。unsigned charこれにより、次の 2 つの疑問が生じます。

1)なぜストリームバッファがそのようなことをするのですか? コード変換は、ストリームバッファの責任から外れているようです-ストリームバッファはストリームを取り、それとの間でデータをバッファリングする必要があります。これ以上何もない。コード変換と同じくらい高レベルなものは、iostream に属するべきだと感じます。

テンプレート化されたストリームバッファを unsigned char で動作させることができなかったので、char に戻り、単に char/unsigned char の間でデータをキャストしました。明らかな理由から、キャストの数を最小限に抑えようとしました。ほとんどのデータは、基本的に read() または write() 関数にまとめられ、その後、基になる streambuf が呼び出されます。(そして、プロセスでキャストを使用します。)読み取り関数は基本的に次のとおりです。

size_t read(unsigned char *buffer, size_t size)
{
    size_t ret;
    ret = stream()->sgetn(reinterpret_cast<char *>(buffer), size);
    // deal with ret for return size, eof, errors, etc.
    ...
}

良い解決策、悪い解決策?


最初の 2 つの質問は、さらに情報が必要であることを示しています。最初に、boost::serialization などのプロジェクトが検討されましたが、それらは独自のバイナリ形式を定義するという点で、より高いレベルに存在します。これは、形式を定義する必要がある場合、または形式が既に定義されている場合、または一括メタデータが不要または不要な場合に、より低いレベルで読み取り/書き込みを行うためのものです。

binary::u32le第二に、修飾子について質問する人もいます。これは、現時点では必要なエンディアンと幅を保持するクラスのインスタンス化であり、将来的には符号付きになる可能性があります。ストリームは、そのクラスの最後に渡されたインスタンスのコピーを保持し、それをシリアル化で使用しました。これはちょっとした回避策でした。最初は << 演算子を次のようにオーバーロードしようとしました。

bostream &operator << (uint8_t n);
bostream &operator << (uint16_t n);
bostream &operator << (uint32_t n);
bostream &operator << (uint64_t n);

しかし、当時、これはうまくいかなかったようです。あいまいな関数呼び出しでいくつかの問題がありました。これは特に定数に当てはまりますが、あるポスターが示唆したように、キャストするか、単に . として宣言することもできconst <type>ます。しかし、他にも大きな問題があったことを覚えているようです。

4

5 に答える 5

2

合法化に賛成です。私はあなたがしていることをほぼ正確に行う必要があり、<</>>のオーバーロードを調べましたが、iostream はそれに対応するように設計されていないという結論に達しました。1 つには、オーバーロードを定義できるようにするために、ストリーム クラスをサブクラス化する必要はありませんでした。

私のソリューション (単一のマシンで一時的にデータをシリアル化する必要があるだけで、エンディアンに対処する必要はありませんでした) は、次のパターンに基づいていました。

// deducible template argument read
template <class T>
void read_raw(std::istream& stream, T& value,
    typename boost::enable_if< boost::is_pod<T> >::type* dummy = 0)
{
    stream.read(reinterpret_cast<char*>(&value), sizeof(value));
}

// explicit template argument read
template <class T>
T read_raw(std::istream& stream)
{
    T value;
    read_raw(stream, value);
    return value;
}

template <class T>
void write_raw(std::ostream& stream, const T& value,
    typename boost::enable_if< boost::is_pod<T> >::type* dummy = 0)
{
    stream.write(reinterpret_cast<const char*>(&value), sizeof(value));
}

次に、POD 以外の型 (文字列など) の read_raw/write_raw をさらにオーバーロードしました。read_raw の最初のバージョンのみをオーバーロードする必要があることに注意してください。ADL を正しく使用すると、2 番目の (1 引数) バージョンは、後で定義された 2 引数のオーバーロードを他の名前空間で呼び出すことができます。

書き込み例:

int32_t x;
int64_t y;
int8_t z;
write_raw(is, x);
write_raw(is, y);
write_raw<int16_t>(is, z); // explicitly write int8_t as int16_t

例を読む:

int32_t x = read_raw<int32_t>(is); // explicit form
int64_t y;
read_raw(is, y); // implicit form
int8_t z = numeric_cast<int8_t>(read_raw<int16_t>(is));

オーバーロードされた演算子ほど魅力的ではなく、簡単に 1 行に収まりません (デバッグ ブレークポイントは行指向であるため、とにかく避ける傾向があります)。より冗長です。

于 2010-03-08T20:43:43.897 に答える
1

私が理解しているように、型を指定するために使用しているストリーム プロパティは、エンディアン、パッキング、またはその他の「メタデータ」値を指定するのにより適しています。型自体の処理はコンパイラが行う必要があります。少なくとも、STL はそのように設計されているようです。

オーバーロードを使用して型を自動的に分離する場合、変数の宣言された型と異なる場合にのみ型を指定する必要があります。

Stream& operator<<(int8_t);
Stream& operator<<(uint8_t);
Stream& operator<<(int16_t);
Stream& operator<<(uint16_t);
etc.

uint32_t x;
stream << x << (uint16_t)x;

宣言された型以外の型を読み取ると、少し面倒になります。ただし、一般的には、出力型とは異なる型の変数への読み書きは避けるべきだと思います。

std::codecvt のデフォルト バージョンは何もせず、すべてに対して「noconv」を返すと思います。「ワイド」文字ストリームを使用する場合にのみ、実際に何かを行います。codecvt に同様の定義を設定できませんか? なんらかの理由で、ストリームに no-op codecvt を定義することが実際的でない場合、特に 1 つの場所に分離されているため、キャスト ソリューションに問題はないと思います。

最後に、独自のシリアル化コードを作成するよりも、Boostなどの標準的なシリアル化コードを使用した方がよいのではないでしょうか?

于 2009-07-19T21:42:52.023 に答える
0

フォーマットされたテキストI/Oと密接に関連しているため、operator<<は使用しません。

実際、これには演算子のオーバーロードをまったく使用しません。別のイディオムを見つけます。

于 2009-07-20T02:26:15.040 に答える
0

私たちはあなたがやっていることと似たようなことをする必要がありましたが、別の道をたどりました. インターフェイスをどのように定義したか興味があります。どのように処理できるかわかりませんが、定義したマニピュレータ(binary::u32le、binaryu16le) の一部です。

basic_streams を使用すると、マニピュレーターは次のすべての要素の読み取り/書き込み方法を制御しますが、サイズ (マニピュレーター情報の一部) が渡される変数の影響を受けるため、おそらく意味がありません。

binary_istream in;
int i;
int i2;
short s;
in >> binary::u16le >> i >> binary::u32le >> i2 >> s;

上記のコードでは、変数が 32 ビット (int が 32 ビットであると仮定) であるかどうかを判断するのは理にかなっていiますが、シリアル化されたストリームから 16 ビットのみを抽出し、完全な 32 ビットを に抽出しますi2。その後、ユーザーは、渡された他のすべてのタイプのマニピュレーターを強制的に導入するか、またはマニピュレーターがまだ有効で、short が渡され、オーバーフローの可能性がある 32 ビットが読み取られたときに、いずれかの方法でユーザーはおそらく予期しない結果を得るでしょう。

サイズは(私の意見では)マニピュレーターに属していないようです。

補足として、私たちの場合、型の実行時定義として他の制約があり、実行時に型を構築するための独自のメタ型システム (バリアントの型) を構築することになり、最終的にはこれらの型の逆シリアル化 (boost スタイル) を実装するため、シリアライザーは基本的な C++ 型ではなく、シリアル化とデータのペアで動作します。

于 2009-07-19T21:52:12.990 に答える