5

最近のチーズの売り上げをすべて含むcheesesales.txtCSVファイルがあります。CheeseSales次のようなことができるクラスを作成したいと思います。

CheeseSales sales("cheesesales.txt"); //has no default constructor
cout << sales.totalSales() << endl;
sales.outputPieChart("piechart.pdf");

上記のコードは、障害が発生しないことを前提としています。実際には、失敗が発生します。この場合、次の2種類の障害が発生する可能性があります。

  • コンストラクターの障害:ファイルが存在しない、読み取り権限がない、無効/解析できないデータが含まれているなどの可能性があります。
  • 通常の方法での失敗:ファイルがすでに存在している、書き込みアクセス権がない、円グラフを作成するために利用できる販売データが少なすぎるなどの可能性があります。

私の質問は単純です:失敗を処理するためにこのコードをどのように設計しますか?

bool1つのアイデア:失敗を示す通常のメソッドからaを返します。コンストラクターの扱い方がわからない。

ベテランのC++コーダーはこれらの種類のことをどのように行いますか?

4

7 に答える 7

4

C ++では、例外はエラーを報告する方法です。ところで、初期化リストの例外を処理できます。

function-try-blockは、ハンドラーseqをctor-initializer(存在する場合)およびfunction-bodyに関連付けます。ctor-initializerでの初期化式の実行中、またはfunction-bodyの実行中にスローされる例外は、tryの実行中にスローされる例外と同じ方法で、function-try-blockのハンドラーに制御を転送します。 -ブロックは制御を他のハンドラーに移します。

優れたコードは通常、ほとんどの上位(スレッド)レベルで最小限のtry/catchブロックを使用する必要があります。理想的には1つだけです。このように、「すべてがスローされる」ことを知っているので、エラーについてあまり考えずに、通常のシナリオのコードフローはきれいに見えます。

于 2012-12-02T10:59:50.840 に答える
2

コンストラクターは、オブジェクトの内部状態を初期化するために使用されます。ファイルの読み取りや処理などの重い操作には使用しないでください。

代わりに、エラーが発生した場合にファイルを読み取り、例外をスローする(または成功した場合はブール値を返す)メソッドを使用してください。メインフローでこの例外をキャッチし、適切と思われる方法で処理します。

編集:これがクラス全体の目的である場合ChessSalesは、データのみを含める必要があり、CSVファイルを読み取り、から読み取った関連データを含むオブジェクトを返すメソッドを持つファクトリクラス(または静的ユーティリティクラスChessSales)を使用する必要がありますCSVファイル。このようにして、データをビジネスロジックから分離します(この場合、CSVファイルの読み取りと解析)

于 2012-12-02T10:00:45.087 に答える
2

まあ、例外をスローすることは明らかな選択です。これには、C ++でいくつかの問題があります。これは、イニシャライザーリストコンストラクターによってスローされた例外をキャッチすることができず、あらゆる種類の問題が発生するためです。

したがって、実際には、ファイルにアクセスして例外をスローする可能性のあるコンストラクターと、オブジェクトを「データがロードされていない状態」のままにするデフォルトのコンストラクターの両方を提供する必要があります。これにより、オブジェクトを他のクラスのメンバーとして安全に使用できると同時に、別のコンストラクターによってデータをロード(または例外スロー)することができます。

もう1つの選択肢は、データ読み込みコンストラクターで例外をスローせず、読み込みに失敗した場合にオブジェクトを無効な状態に設定し、他のメソッドで例外をスローし、現在の状態のゲッターを設定することです。

いずれにせよ、クラスにはエラー/初期化されていない状態が必要ですが、それを回避する安全な方法はないと思います。

@MathieuM。のコメントによる編集:外部で「初期化されていない状態」を実現するための1つの代替方法は、ポインターラッパークラスを使用して最も簡単にオプションにすることです。次に、初期化子リストで安全にNULLに初期化され、実際の初期化は、エラー処理を使用してコンストラクター本体で試行されます。これを選択することで、コンストラクターに例外をスローさせ、クラスのユーザーにそれを心配させることができます。

于 2012-12-02T10:29:12.860 に答える
2

あなたは2種類の失敗を区別するのは正しいです、確かにそれらは微妙に異なります。


コンストラクターの障害:ファイルが存在しない、読み取り権限がない、無効/解析できないデータが含まれているなどの可能性があります。

例外をスローするだけです。ハーフビルドオブジェクトは、バグの多いプログラムへの最短の方法です。

クラスには、コンストラクターが確立し、すべてのメソッドが維持する不変条件が必要です。コンストラクターが不変条件を確立できない場合、オブジェクトは使用できません。C++でこれを報告する最良の方法は、言語がハーフビルドオブジェクトが使用されないように例外をスローすることです。

無効な状態が必要である可能性があると人々が提案した場合は、単一責任の原則を思い出してください。クラスにはすでに特定の責任(不変)があり、さらに多くを希望する場合は、オプションを提供するための専用のクラスにカプセル化できます。私の頭から離れて、そして両方とも優れた選択肢です。boost::optionalstd::unique_ptr


通常の方法での失敗:ファイルがすでに存在している、書き込みアクセス権がない、円グラフを作成するために利用できる販売データが少なすぎるなどの可能性があります。

残念ながら、次の2つのケースを区別できませんでした。

  • インスタンスを読み取るだけのメソッド
  • インスタンスも変更するメソッド

すべての方法で、エラー報告戦略を選択する必要があります。私のアドバイスは、例外例外的であるということです。障害が例外的であると見なされる場合(99.99%の確率でネットワークリンクがダウンしている場合)、例外は問題ありません。一方、失敗が予想される場合は、通常、findメソッドなどの入力、または場合writeによっては指定されたファイルへのメソッドに応じて、ユーザーに適切に対応する機会を与える必要があります。

例外が除外された後、少なくとも2つの方法が残っています。

  • 操作がうまくいったかどうかを示すコード( bool、 )を返すenum
  • 問題が発生した場合に呼び出されるエラーポリシーを提供するようにユーザーに求める

エラーポリシーは、enum(スキップ、再試行-1回、スロー)のように単純な場合もあれば、さまざまな方法で本格的に実行される場合のように複雑な場合もありStrategyます。

さらに、あなたのメソッドにエラー報告メカニズムが1つしかない可能性があるとは誰も言いません。たとえば、次のように選択できます。

  • ファイルがすでに存在する場合にエラーポリシーを呼び出すには(結局のところ、ユーザー提供、別の名前に切り替えたい場合があります)
  • ディスクにアクセスできない場合にスローします(ハードウェアは一般的に機能すると予想されます!)

最後に、これに加えて、インスタンスも変更するメソッドは、コンストラクターが確立した不変条件を維持することを心配する必要があります。メソッドが不変条件を台無しにする可能性がある場合、それはまったく不変条件ではなく、ユーザーはクラスが使用されるたびに恐れて震える必要があります...一般的な知恵は、オブジェクトの変更を開始する前にスローする可能性のあるすべての操作を実行することです。

単純な(しかし非常に簡単な)実装は、コピーとスワップのイディオムです。オブジェクトを内部的にコピーし、コピーに対して操作を実行し、メソッドの最後でコピーの状態を現在のオブジェクトの状態とスワップします。何か問題が発生した場合、コピーは破損し、スタックの巻き戻し中にすぐに破棄され、オブジェクトはそのままになります。

このトピックの詳細については、例外保証についてお読みください。私が説明したのは、データベーストランザクション(すべてまたはまったく)に似た、StrongExceptionGuaranteeメソッドの典型的な実装です。

于 2012-12-02T11:55:28.807 に答える
0

オブジェクトを作成する前に、入力ファイルから情報を取得できると思います。たとえば、コードは次のようになります。

if(!getInfoFromFile("cheesesales.txt", date, amount, kindOfCheese, money)){
    cout << "Failed to get information from file." << endl;
    return FALSE;
}
CheeseSales sales(date, amount, kindOfCheese, money);
cout << sales.totalSales() << endl;
sales.outputPieChart("piechart.pdf");

そうすれば、コンストラクターでエラーを処理することを回避できます。
もちろん、例外をスローすることも解決策です。しかし、C ++の例外は非常に複雑なので、私はそれが好きではありません。想像を絶する問題がたくさん発生する可能性があります。

于 2012-12-02T10:47:58.323 に答える
0

これが私がエラーを処理する方法です:私はすべてのエラーをログファイルに記録し、エラーを報告するための使いやすい関数を作成します。プログラムがうまく動作しない場合は、ログファイルを開いて理由を見つけます。

発生したエラーを知る方法について:C ++に例外を取り込む理由の1つは、コンストラクターでエラーを報告することでした。コンストラクターは値を返さないため、この方法で例外をスローして失敗を報告できます。個人的には、このスキームはあまり好きではありません。コードを内に配置できるからtry..catchです。それを忘れると、なぜプログラムがクラッシュしたのか不思議に思うかもしれません。

だから私が通常することはこれです:

1)コンストラクターの操作の成功に応じて、コンストラクターに成功/失敗のメンバー変数を設定させます。後でのようなもので確認できif (myobj.constructor_ok()...)ます。メンバー変数に直接アクセスしなかったことに注意してください。

2)メソッドからtrue / falseを返すのがとても好きで、可能な限り成功/失敗を示します。これにより、コードが非常に読みやすくなります。

3)..および上記のログファイル。

于 2012-12-02T10:19:37.837 に答える
0

この質問をするときに重要な詳細を見逃しました:操作が失敗したときに何をしたいですか?

コンストラクターの場合は間違いなく例外をスローします。上で書いたのと同じようにコードを書けるようにしたいと思います。こんな風に見せたくない

CheeseSales sales("cheesesales.txt"); //has no default constructor
if (!sales.good()) {
    // Somehow handle things here.
}
// The if block has to break control flow or reinitialise sales, otherwise
// these next lines will break.
cout << sales.totalSales() << endl;
sales.outputPieChart("piechart.pdf");

投げることは、この場合、はるかに強い不変量を想定できることを意味し、したがって、良いことです。

出力操作はの状態を変更しないので、CheeseSalesそこにスローしても不変条件はそれほど強化されません。その場合、チャートを印刷するためにヘルパーオブジェクトを使用する方が良い場合があります。

ChooseSales sales("cheesesales.txt");
PdfStream chart("piechart.pdf");
chart << sales.asPieChart();

PdfStream可能性はありますがstd::ofstream、さらにいくつかの機能を提供したい場合があります。)

操作が失敗すると、chartオブジェクトがエラー状態になる可能性があります。これは、iostreamが一般的にどのように機能するかに対応します。

于 2012-12-02T10:54:10.803 に答える