C ++でのエラー処理のためのtry-catchまたはifs


30

例外はゲームエンジンの設計で広く使用されていますか、それとも純粋なifステートメントを使用する方が望ましいですか?例外の例:

try {
    m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
    m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
    m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
    m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
    m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
    m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
} catch ... {
    // some info about error
}

ifsを使用した場合:

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
if( m_fpsTextId == -1 ) {
    // show error
}
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
if( m_cpuTextId == -1 ) {
    // show error
}
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
if( m_frameTimeTextId == -1 ) {
    // show error
}
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
if( m_mouseCoordTextId == -1 ) {
    // show error
}
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
if( m_cameraPosTextId == -1 ) {
    // show error
}
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
if( m_cameraRotTextId == -1 ) {
    // show error
}

私は、例外がifsより少し遅いと聞いたので、if-checkingメソッドと例外を混ぜてはいけません。ただし、例外として、initialize()メソッドなどのたびに大量のifよりも読みやすいコードがありますが、私の意見では、たった1つのメソッドには重すぎる場合があります。彼らはゲーム開発のために大丈夫ですか、それとも単純なifに固執しますか?


Boostはゲーム開発に悪いと多くの人が主張していますが、["Boost.optional"](boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html)を参照することをお勧めしますあなたのIFS例はほとんどよりよい
ManicQin

Andrei Alexandrescuによるエラー処理についての良いがあります。私は彼と例外なく同様のアプローチを使用しました。
テルビン

回答:


48

簡単な答え:Niklas FrykholmによるSensible Error Handling 1Sensible Error Handling 2、およびSensible Error Handling 3を読んでください。実際に、そのブログのすべての記事を読んでください。私はすべてに同意するとは言いませんが、そのほとんどは金です。

例外を使用しないでください。理由はたくさんあります。主要なものをリストします。

新しいコンパイラではかなり遅くなりますが、実際には遅くなります。一部のコンパイラは、実際に例外をトリガーしないコードパスに対して「ゼロオーバーヘッド例外」をサポートします(ただし、例外処理に必要な余分なデータがまだあるため、実行可能ファイル/ dllサイズが肥大化するため、少し嘘になります)。ただし、最終結果は、はい、例外の使用が遅くなり、パフォーマンスが重要なコードパスでは、絶対に回避する必要があります。コンパイラーによっては、それらを有効にするとオーバーヘッドが追加される場合があります。コードサイズは常に大きくなり、多くの場合かなり大きくなります。これは、今日のハードウェアのパフォーマンスに深刻な影響を与える可能性があります。

例外は、コードより脆弱にします。悪名高いグラフィック(残念ながら、今は見つけられません)がありますが、これは基本的に、例外安全なコードと例外安全でないコードを書くことの難しさをグラフ上に示しているだけです。単に例外を伴う小さな落とし穴がたくさんあり、例外的に安全に見えるが実際にはそうではないコードを書く多くの方法があります。C ++ 11委員会全体がこれを馬鹿にし、 std :: unique_ptrを正しく使用するための重要なヘルパー関数を追加するのを忘れており、それらのヘルパー関数を使用しても、使用するよりも多くの入力が必要であり、ほとんどのプログラマーはそうしません」もしそうでないなら、何が悪いのかを理解することすらできません。

より具体的には、ゲーム業界向けに、一部のコンソールのベンダー提供のコンパイラ/ランタイムは、例外を完全にサポートしていないか、まったくサポートしていません。コードで例外を使用している場合、この日も年齢もコードの一部を書き直して新しいプラットフォームに移植する必要がありますか?(コンソールがリリースされてから7年間で変更されたかどうかは不明です。例外を使用せず、コンパイラ設定でも例外を無効にしているため、最近話した人が誰もチェックしていないこともわかりません)

一般的な考え方は非常に明確です。例外的な状況には例外を使用します。あなたのプログラムが「何をすべきかわからない、おそらく誰か他の人がそうするかもしれないので、例外を投げて何が起こるか見てみよう」になったときにそれらを使用してください。他のオプションが意味をなさないときにそれらを使用します。誤って少量のメモリをリークしたり、適切なスマートハンドルの使用を台無しにしたためにリソースをクリーンアップできなかったりすることを気にしない場合に使用します。他のすべての場合では、それらを使用しないでください。

あなたの例のようなコードに関して、あなたは問題を解決するいくつかの他の方法を持っています。より堅牢な方法の1つは、単純な例では必ずしも理想的ではありませんが、単項エラータイプに傾くことです。つまり、createText()は整数ではなくカスタムハンドルタイプを返すことができます。このハンドルタイプには、テキストを更新または制御するためのアクセサーがあります。ハンドルがエラー状態になった場合(createText()が失敗したため)、ハンドルへの以降の呼び出しは単純に失敗します。また、ハンドルにクエリを実行して、エラーが発生したかどうか、発生した場合は最後のエラーを確認することもできます。このアプローチは他のオプションよりもオーバーヘッドが大きくなりますが、かなり安定しています。本番環境では単一の操作が失敗する可能性があるが、できない/できない/できないという状況で、長い操作の文字列を実行する必要がある場合に使用します

モナドのエラー処理を実装する代わりに、カスタムハンドルオブジェクトを使用するのではなく、コンテキストオブジェクトのメソッドが無効なハンドルIDを適切に処理するようにします。たとえば、createText()が失敗したときに-1を返す場合、-1が渡されると、これらのハンドルのいずれかを取るm_statisticsの呼び出しは正常に終了します。

同様に、実際に失敗している関数内にエラー印刷を配置できます。あなたの例では、createText()にはおそらく何が間違っていたかについての詳細な情報があるため、より意味のあるエラーをログにダンプすることができます。この場合、呼び出し元にエラー処理/印刷をプッシュしてもほとんど利点はありません。呼び出し元が処理をカスタマイズする必要がある場合(または依存関係の注入を使用する場合)に行います。エラーがログに記録されるたびにポップアップできるゲーム内コンソールを用意することは良い考えであり、ここでも役立ちます。

統計システム用のテキストBLOBを作成するという単純な行為のように、健全な環境で失敗することはないと思われる呼び出しに対する最良のオプション(上記のリンクされたシリーズで既に提示されています)は、失敗しました(この例ではcreateText)中止します。何かが完全に削除されない限り、createText()が本番環境で失敗しないことを合理的に確信できます(たとえば、ユーザーがフォントデータファイルを削除したか、何らかの理由で256MBのメモリしかないなど)。これらのケースの多くでは、障害が発生したときに行うべきことすらありません。メモリ不足?ユーザーにOOMエラーを表示するための素晴らしいGUIパネルを作成するために必要な割り当てを行うことさえできないかもしれません。フォントが見つかりませんか?エラーをユーザーに表示することを困難にします。何が間違っていても、

(a)エラーをログファイルに記録し、(b)通常のユーザーアクションに起因しないエラーに対してのみクラッシュを行う限り、クラッシュはまったく問題ありません。

可用性が重要で、ウォッチドッグの監視が十分ではない多くのサーバーアプリでは、同じことはまったく言いませんが、ゲームクライアントの開発とはまったく異なります。また、他の言語の例外処理機能はC ++のように噛みつかない傾向があるため、C / C ++を使用することは避けてください。これらの環境は管理された環境であり、C ++の持つ例外安全性の問題はすべてありません。サーバーは、ゲームクライアントのような最小遅延保証よりも並列性とスループットに重点を置く傾向があるため、パフォーマンスの懸念も軽減されます。シューティングゲームなどのアクションゲームサーバーでさえも、たとえばC#で記述された場合、FPSクライアントのようにハードウェアを限界までプッシュすることはめったにないため、非常に機能します。


1
+1。さらにwarn_unused_result、一部のコンパイラで機能するような属性を追加して、未処理のエラーコードをキャッチできるようにする可能性があります。
マチェイピエチョトカ

タイトルにC ++が表示されていて、モナドのエラー処理が既に存在しないことは間違いありませんでした。いい物!
アダム

パフォーマンスが重要ではなく、主に高速な実装を目指している場所はどうですか?たとえば、ゲームロジックを実装する場合
Ali1S232

また、デバッグ目的だけに例外処理を使用するというアイデアについてはどうでしょうか?ゲームをリリースするとき、すべてが問題なくスムーズに進むことを期待しています。そのため、例外を使用して開発中にバグを見つけて修正し、後でリリースモードでそれらの例外をすべて削除します。
Ali1S232

1
@Gajoo:ゲームロジックの場合、例外はロジックの追跡を難しくします(すべてのコードの追跡を難しくします)。私の経験では、ゲームロジックでPythnnとC#の例外が発生することはまれです。デバッグの場合、通常、ハードアサートははるかに便利です。スタックの大部分を解き、例外を投げるセマンティクスのためにあらゆる種類のコンテキスト情報を失った後ではなく、何かがうまくいかない瞬間に中断して停止します。デバッグロジックでは、ツールを構築してGUIを編集し、設計者が検査および調整できるようにします。
ショーンミドルディッチ

13

ドナルドクヌース自身の古い知恵は次のとおりです。

「私たちは小さな非効率性を忘れて、約97%の時間を言うべきです。時期尚早な最適化はすべての悪の根源です。」

これを大きなポスターとして印刷し、本格的なプログラミングを行う場所ならどこにでも置いてください。

したがって try / catchが少し遅い場合でも、デフォルトで使用します。

  • コードはできるだけ読みやすく、理解しやすいものでなければなりません。あなたは可能性が一度コードを書いていますがします、このコードをデバッグするため、他のコードをデバッグするため、理解するための、改善のためにそれをより多くの回を読みます...

  • パフォーマンスに対する正確さ。if / elseを正しく行うことは簡単ではありません。この例では、1つのエラーが表示されるのではなく、複数のエラーが表示される可能性があるため、誤って実行されます。カスケードif / then / elseを使用する必要があります。

  • 一貫した使いやすさ:Try / catchは、すべての行でエラーをチェックする必要のないスタイルを採用しています。一方:一つ欠けている場合/ elseとあなたのコードのかもしれないの難破船の大混乱。もちろん、再現不可能な状況でのみ。

最後のポイント:

例外はifsより少し遅いと聞きました

約15年前、最近信頼できる情報源から聞いたことがないと聞いた。コンパイラは改善されているかもしれません。

要点は、時期尚早な最適化を行わないことです。手元のコードが頻繁に使用されるコードのタイトな内部ループであり、切り替えスタイルによってパフォーマンスが大幅に向上することをベンチマークで証明できる場合にのみ実行してください。これは3%のケースです。


22
これを無限に-1する。このクヌースの引用が開発者の脳に与えた害を推測することはできません。例外を使用するかどうかは時期尚早の最適化ではなく、設計上の決定であり、パフォーマンスをはるかに超える重要な設計上の決定です。
サムホセバル

3
@SamHocevar:申し訳ありませんが、私はあなたのポイントを得ることができません:質問、例外を使用するときに起こり得るパフォーマンスヒットについてです。私のポイント:それについて考えないでください。ヒットはそれほど悪くはありません(もしあれば)、他のものははるかに重要です。ここで、私のリストに「設計決定」を追加することに同意するようです。OK。一方、Knuthの引用は悪いと言って、時期尚早の最適化が悪くないことを意味します。しかし、これがまさにここで起こったことですIMO:Qは、アーキテクチャ、設計、または異なるアルゴリズムについては考えず、例外とそれらのパフォーマンスへの影響についてのみ考えます。
AH

2
私はC ++の明快さに反対します。C ++には未チェックの例外があり、一部のコンパイラはチェック済みの戻り値を実装しています。その結果、コードはより明確に見えますが、メモリリークが隠されたり、コードが未定義の状態になったりする可能性があります。また、サードパーティのコードは例外に対して安全ではない場合があります。(状況は、例外、GCなどをチェックしたJava / C#では異なります。)設計の決定時点-APIポイントを超えない場合、perl one-linersを使用して、各スタイルから/へのリファクタリングを半自動で実行できます。
マチェイピエチョトカ

1
@SamHocevar: " 質問は、何が望ましいかについてであり、何がより速いかについてではありません。 "質問の最後の段落を読み直してください。唯一、彼は、彼らが遅くなる可能性があると考えているので、彼はさらに約例外を使用していない考えている理由があります。OPが考慮に入れていない他の懸念があると思われる場合は、それらを投稿するか、投票した人に賛成してください。しかし、OPは例外のパフォーマンスに非常に明確に焦点を合わせています。
ニコルボーラス

3
@AH-クヌースを引用する場合は、残りを忘れないでください(そしてIMOが最も重要な部分です)。そのような推論によって自己満足に落ち着いて、彼は重要なコードを注意深く見るのが賢明でしょう;しかし、そのコードが特定された後にだけです。多くの場合、これはまったく最適化しない、またはパフォーマンスが最適化ではないが実際には基本的な要件である場合にコードが遅いことを正当化しようとする言い訳と見なされます。
マキシマスミニマス

10

この質問に対する回答は既に非常に優れていますが、特定のケースでのコードの可読性とエラー回復についての考えを追加したいと思います。

あなたのコードは実際には次のように見えると思います:

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );

例外も、ifもありません。エラー報告はで行うことができますcreateText。またcreateText、戻り値を確認する必要のないデフォルトのテクスチャIDを返すことができるため、残りのコードも同様に機能します。

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