2016-03-23 12 views
2

私は、ライターが完了したことを示す明示的なメッセージをファイルに書きたいファイルフォーマットを作成しています。私は過去に、生成プログラムがクラッシュしたところでファイルを生成することで問題を抱えていました。明示的なマーカーがないと、ファイルを検出するためのプログラムを読む方法が不完全であるため、私は認識せずにファイルを切り捨てました。RAIIをファイルマーカーの終わりに書き込むには?

私はこれらのファイルを書くために使用されるクラスを持っています。通常は、「オープン」操作と「クローズ」操作がRAIIを使用する場合は、デストラクタにファイルマーカーの終わりを書き込むコードを挿入します。このようにして、ユーザーは忘れることができません。しかし、例外がスローされて書き込みが完了しない状況では、デストラクタは引き続き実行されます。その場合、メッセージを書きたくないので、読者はファイルが不完全であることを知ることができます。

これは「コミット」操作がいつでも発生する可能性があります。 RAIIがコミットするのを忘れることはできないようにしたいが、例外が発生したときにコミットしたくない。ここの誘惑はstd :: uncaught_exceptionsを使うことですが、それはコードの匂いだと思います。

これに対する通常の解決方法は何ですか?人々が覚えていることだけを要求する私は、誰かが私のAPIを使用しようとするたびに、これが戸惑いになることに懸念しています。

+0

ハーブ・サッター(Herb Sutter)は、この件に関する良い記事を掲載しています。 http://www.gotw.ca/gotw/047.htm彼の推奨は、ユーザーにファイルを明示的に閉じる(コミット)ように強制することです。さもなければそれは有効です。 –

答えて

0

この問題に取り組む1つの方法は、書き込みの最後に完全に埋められるだけのヘッダーを定義できる単純なフレーミングシステムを実装することです。ヘッダーをファイルの内容の検証に役立てるためにSHA256ハッシュを含めます。これは通常、ファイルの最後にバイトを読み込むよりもはるかに便利です。

いくつかのフィールドを意図的にゼロにしてヘッダーを書き出し、ペイロードの内容をハッシュ方法で入力しながら書き出し、ヘッダーに戻って最後に書き直します値。ファイルは明らかに無効な状態から始まり、すべてが完了するまで有効である場合、のみがになります。

実装の詳細を処理するストリームハンドルでこれをまとめてもかまいません。これは、呼び出しコードが通常のファイルを開くことに関する限りです。ヘッダーが不完全または無効な場合は、読み取りバージョンで例外がスローされます。

+0

私が正しいことを理解すれば、これはちょうど道の下で缶をキックするように聞こえる。デストラクターには、ヘッダーを埋めるためにファイルの先頭に戻るコードが含まれているか、そうするためにユーザーが明示的なメソッドを呼び出す必要があり、正方形に戻ります。 –

+0

なぜそれを言うのですか?ファイルを強制的にクローズしてヘッダーをファイナライズするオプションのメソッド、またはヘッダーがまだ終了していない場合はそれを行うデストラクタ・メソッドです。これは、コンストラクタがファイルをきれいに開き、デストラクタが必要なファイナライズを処理したときに、RAIIと非常によく似ています。 – tadman

+0

問題は例外間の相互作用であり、コミットはエラーフリーの場合に行われなければならず、ユーザーに手動でコミットを要求することはエラーが発生する可能性があります。手動の方法がある場合、彼らはしばしばそれを呼び出すことを忘れてしまいます。 std :: uncaught_exceptionを使用せずにデストラクタで作業する場合、デストラクタにいる理由を伝えることはできません(例外がスローされた場合はマーカー*を書きたくないため、消費者はファイルが不完全です)。 –

0

あなたのクラスのユーザーがファイルへの書き込みを完了したときに呼び出すcommitメソッドを追加すると、RAIIが正常に動作するようです。

class MyFileFormat { 
public: 
    MyFileFormat() : committed_(false) {} 
    ~MyFileFormat() { 
     if (committed_) { 
      // write the completion footer (I hope this doesn't throw!) 
     } 
     // close the underlying stream... 
    } 

    bool open(const std::string& path) { 
     committed_ = false; 
     // open the underlying stream... 
    } 

    bool commit() { 
     committed_ = true; 
    } 
}; 

責任は、彼らが完了したら、commitを呼び出すために使用者にありますが、少なくともあなたは、リソースが閉じられ得ることを確認することができます。

このような場合のより一般的なパターンについては、ScopeGuardsをご覧ください。

ScopeGuardは、クラスからのクリーンアップの責任を持ち、ScopeGuardが有効範囲外になり、明示的に破棄される前に破棄された場合に任意の "クリーンアップ"コールバックを指定するために使用できます。あなたのケースでは、失敗クリーンアップ(クローズファイルハンドルなど)と成功クリーンアップ(書き込み完了フッターとクローズファイルハンドルなど)の両方のコールバックをサポートするというアイディアを拡張することができます。

+0

私が質問で尋ねようとしているのは、基本的にケーキを食べて食べることができるということです。ユーザーがコミットメソッドを呼び出して安全性を覚えておく必要はありません。私はコミットを延期することをデストラクターまでやらないと思っていませんでしたが、私はそれに何の利益も見ません。 –

+0

私は、はいを参照してください - これはその質問に答えることはありません。そして実際、私はそれを見るので、デストラクタにコミットアクションを延期するのは良い考えではないようです。 – lobudge

0

私は一時ファイルに書き込むことで、そのような状況を処理しました。ファイルに追加している場合でも、ファイルの一時的なコピーに追加してください。

デストラクタでは、std::uncaught_exception()をチェックして、テンポラリファイルを目的の場所に移動するかどうかを判断できます。

関連する問題