C ++コードで例外の安全性をどの程度重要と考えていますか?


19

コードを非常に例外安全にすることを検討するたびに、非常に時間がかかるため、それを行わないことを正当化します。この比較的単純なスニペットを考えてみましょう。

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

基本的な例外保証を実装するには、new呼び出しでスコープポインターを使用できます。これにより、呼び出しのいずれかが例外をスローする場合のメモリリークを防ぐことができます。

ただし、強力な例外保証を実装したいとします。少なくとも、コンテナの共有ポインタ(Boostを使用していない)、Entity::Swapコンポーネントをアトミックに追加するためのnothrow 、およびの両方にアトミックに追加するための何らかのイディオムを実装する必要がVectorありMapます。これらは実装に時間がかかるだけでなく、例外の安全でないソリューションよりも多くのコピーを必要とするため、高価になります。

最終的には、単純なCreateEntity関数が非常に例外安全であるように、そのすべてを行うために費やした時間が正当化されないように思えます。とにかく、ゲームにエラーを表示して、その時点で終了させたいだけでしょう。

あなたは自分のゲームプロジェクトでこれをどのくらい受け止めていますか?例外が発生したときにクラッシュする可能性のあるプログラムに対して、例外の安全でないコードを記述することは一般に許容されますか?


21
過去にC ++プロジェクトに取り組んだとき、基本的には例外が存在しないふりをしました。
テトラッド

2
私は個人的に例外を愛し、実際にコードが強力な保証を提供する方法を見つけ出すことを実際に楽しんでいます。ただし、例外が発生したときにクラッシュするだけの場合は、気にしないでください。

7
私は個人的に例外を使用して管理します。たとえば、関数がクラッシュする可能性があることを知っている場合は、クラッシュさせますが、関数がアーカイブを正常に読み取れない可能性があることを知っているが、別の方法がある場合は、try、catchで囲みます。「読み取り不可」の例外の場合、アーカイブを読み取らずに他の方法で管理します。
グスタボマシエル

Gtoknuが言ったように、外部ライブラリーがスローできる例外を認識する必要があります。
ピーターØlsted12年

回答:


26

例外を使用する場合(言うべきではありませんが、この質問の特定の範囲外にあるそのアプローチには賛否両論があります)、適切に例外セーフコードを作成する必要があります。

例外を使用せず、明示的に例外セーフではないコードは、例外を使用するが例外の安全性を半ば主張するコードよりも優れています。後者の場合、おそらくまったく回復できない例外が発生したときに発生する可能性があるバグと障害のクラスがあり、例外の利点の1つを完全に無効にします。たとえ比較的まれなクラスの障害であっても、それは可能です。

これは、例外を使用する場合、すべてのコードが強力な例外保証を提供する必要があることを意味するものではありません-各コードが何らかの種類(なし、基本、強力)の保証を提供する必要があるため、そのコードの消費において消費者が提供できる保証を知っています。一般的に、問題のコンポーネントが低レベルであるほど、保証は強くなります。

強力な保証を提供したり、例外処理パラダイムやその他の余分な作業や短所を実際に採用したりしない場合は、それが示すすべての利点を得ることはできず、より簡単になる可能性があります例外を完全に無視する時間です。


12
これだけでも+1の価値があります。「例外を使用せず、明示的に例外セーフではないコードは、例外を使用するが例外の安全性を半減するコードよりも優れています。」
マキシマスミニマス

14

私の一般的な経験則:例外を使用する必要がある場合は、例外を使用してください。例外を使用する必要がない場合は、例外を使用しないでください。例外を使用する必要があるかどうかわからない場合は、例外を使用する必要はありません。

常時オンのミッションクリティカルなアプリケーションでは、絶対的な最後の防衛線となる例外があります。絶対に失敗することのない航空管制ソフトウェアを作成している場合は、例外を使用してください。原子力発電所の制御コードを書いている場合は、例外を使用してください。

一方、ゲームを開発するときは、次のマントラに従うことをお勧めします。早い段階でクラッシュし、大声でクラッシュします。問題がある場合は、できるだけ早くそれを知りたいと思うでしょう。エラーが目に見えないように「処理」されることは望ましくありません。これにより、後の誤動作の実際の原因が隠され、必要以上にデバッグが難しくなるためです。


5

例外の問題は、とにかくそれらに対処しなければならないことです。メモリ不足により例外が発生した場合は、とにかくそのエラーを処理できない可能性があります。同様に、ゲーム全体をエラーボックスを表示する1つの巨大な例外ハンドラーにダンプすることもできます。

ゲーム開発では、可能な場合にコードをエラーが発生しないように優雅に継続させる方法を試してみてください。

たとえば、ゲームに特別なERRORメッシュをハードコーディングします。白いXと白い境界線が付いた回転する赤い円です。ゲームによっては、見えないもののほうが良いかもしれません。起動時に初期化されるシングルトンインスタンスが1つあります。外部ファイルを使用するのではなくコードに組み込まれているため、失敗することはないはずです。

エラーをスローバックしてデスクトップにクラッシュする代わりに、通常のloadMesh呼び出しが失敗した場合、コンソール/ログファイルにエラーを静かに記録し、ハードコーディングされたエラーメッシュを返します。MODが物事を台無しにしたとき、Skyrimは実際に同じことを行うことがわかりました。プレイヤーがエラーメッシュを見ることさえない可能性があります(何らかの大きな問題があり、それらの多くがそれに似ている場合を除く)。

フォールバックシェーダーもあります。基本的な頂点マトリックス操作を行うものですが、何らかの理由で均一なマトリックスがない場合でも、直交ビューを行う必要があります。適切なシェーディング/ライティング/マテリアルはありません(ただし、color_overideフラグがあるため、エラーメッシュで使用できます)。

唯一の問題は、エラーメッシュバージョンが何らかの方法でゲームを完全に中断する可能性がある場合です。たとえば、プレイヤーがエリアをロードすると、エラーメッシュがキックされて床に落ちる可能性があるため、物理学が適用されないことを確認してから、今度は正しいメッシュを使用してゲームをリロードすると、それが大きい場合は地面に埋め込まれています。おそらく物理メッシュをグラフィカルメッシュと一緒に保存するので、それほど難しくないはずです。スクリプティングも問題になる可能性があります。いくつかのものを使用すると、ハード死ぬ必要があります。フォールバック「レベル」を使用するかどうかは確かではありません(エラーが発生したことを示す標識のある小さな部屋を作ることはできますが、プレイヤーをそこに閉じ込めたくないので、保存が無効になっていることを確認してください)。

「新規」の場合、ゲーム開発のために、何らかの工場の使用を検討する方が良いかもしれません。オブジェクトを実際に必要とする前に事前に割り当てたり、オブジェクトをまとめて作成したりするなど、さまざまな利点を得ることができます。また、メモリ管理に使用できるオブジェクトのグローバルインデックスのようなものを作成し、IDで物事を見つけやすくします(ただし、コンストラクターでもできます)。そしてもちろん、そこでも割り当てエラーを処理できます。


2

実行時チェックとクラッシュの欠落に関する大きな問題は、ハッカーがクラッシュを使用してプロセスを引き継ぎ、その後マシンを引き継ぐ可能性があることです。

現在、ハッカーは実際のクラッシュを悪用することはできません。クラッシュが発生すると、プロセスは無用で無害です。ヌルポインターから単純に読み取る場合、それはクリーンクラッシュであり、間違いを犯し、プロセスは即座に停止します。

技術的に違法ではないが、意図したものではなかったコマンドを実行すると危険が生じます。ハッカーは、慎重に作成されたデータを使用して、そうでなければ安全なはずのアクションを指示できる可能性があります。

キャッチされない例外は実際のクラッシュではなく、プログラムを終了するための単なる方法です。特定の状況で例外をスローするライブラリを誰かが書いたために、例外が発生するだけです。通常は、ハッカーの入り口となる予期しない状況に陥らないようにします。

実際には、危険な動きは例外をスリップさせるのではなくキャッチすることです。つまり、決してそうすべきではないということではなく、処理できるとわかっているものだけをキャッチし、残りをスリップさせてください。そうしないと、ライブラリに慎重に入れた安全対策を取り消す危険があります。

配列の終わりを超えて書き込むなど、必ずしも例外をスローしないエラーに焦点を当てます。あなたのプログラムは偶然それをすることができないでしょうか?


2

例外を調べ、それらを引き起こす可能性のある条件を調べる必要があります。長い時間をかけて、開発/デバッグビルド/テストサイクル中に適切なチェックを行うことで、人々が例外を使用する大部分を回避できます。アサーションを使用し、参照を渡し、常に宣言でローカル変数を初期化し、すべての割り当てをmemset-zeroし、解放後NULLなどを使用します。そして、非常に多くの理論上の例外ケースがなくなります。

考えてみましょう:何かが失敗し、例外をスローする候補になる可能性がある場合-影響は何ですか?それはどれくらい重要ですか?ゲームでメモリ割り当てに失敗した場合(元の例を取り上げる場合)、一般的に、障害ケースを処理する方法よりも大きな問題が手にあり、例外を使用して障害ケースを処理しても大きな問題にはなりませんどこかに行って。さらに悪いことに、実際には大きな問題がそこにあるという事実を隠すかもしれません。あなたはそれが欲しいですか?知らない。メモリ割り当てに失敗した場合、クラッシュして恐ろしく燃やし、できるだけ障害点に近づいて、デバッガに侵入して何が起こっているのかを把握し、適切に修正できるようにすることを知っています。


1

SEで他の誰かを引用するのが適切かどうかはわかりませんが、他の答えはどれもこれに本当に触れていないと感じています。

ジェイソン・グレゴリーの著書 『Game Engine Architecture』から引用。(素晴らしい本!)

「構造化例外処理(SEH)は、プログラムに多くのオーバーヘッドを追加します。すべてのスタックフレームは、スタックのアンワインドプロセスに必要な追加情報を含めるために拡張する必要があります。また、プログラム(またはライブラリ)の1つの関数でもSEHを使用する場合は、プログラム全体でSEHを使用する必要があります。コンパイラーは、呼び出しスタックでどの関数が自分より上にあるかを知ることができません例外をスローします。」

そうは言っても、私が構築している私のエンジンは例外を使用していません。これは、前述の潜在的なパフォーマンスコストと、すべての目的でエラーリターンコードが正常に機能するためです。私が本当に失敗を探す必要があるのは、リソースの初期化と読み込みであり、その場合、成功を示すブール値が正常に機能することを返します。


2
構造化例外処理は、Windowsで使用可能な特定の種類の例外処理であり、一般にC ++例外とは異なります。
キロタン

ドー、私はこれを知らなかったとは信じられない。修正してくれてありがとう!
-KlashnikovKid

0

例外が生成またはキャッチされるたびに何かをログに記録できるため、コードの例外を常に安全にします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.