今日、私の C++ マルチプラットフォーム コードでは、すべての関数の周りに try-catch があります。すべての catch ブロックで、現在の関数の名前を例外に追加して再度スローします。これにより、一番上の catch ブロック (最終的に例外の詳細を出力する場所) に完全な呼び出しスタックがあり、例外の原因を追跡するのに役立ちます。
それは良い習慣ですか、それとも例外のコールスタックを取得するためのより良い方法はありますか?
あなたがしていることは良い習慣ではありません。理由は次のとおりです。
1. 不要です。
デバッグ情報が生成されるようにプロジェクトをデバッグ モードでコンパイルすると、GDB などのデバッガーで例外処理のバックトレースを簡単に取得できます。
2.面倒です。
これは、すべての関数に追加することを覚えておく必要があるものです。たまたま関数を見落とした場合、特にそれが例外の原因となった関数である場合は、大きな混乱が生じる可能性があります。そして、あなたのコードを見ている人は、あなたが何をしているのかを理解する必要があります. また、__FUNC__ または __FUNCTION__ または __PRETTY_FUNCTION__ などを使用したに違いありませんが、これらはすべて非標準です (C++ には関数の名前を取得する標準的な方法はありません)。
3. 遅いです。
C++ での例外の伝播はすでにかなり遅く、このロジックを追加してもコードパスが遅くなるだけです。これは、コードのリリース バージョンでキャッチと再スローを簡単に省略できるマクロを使用してキャッチと再スローを行う場合には問題になりません。そうしないと、パフォーマンスに問題が生じる可能性があります。
いい練習
スタック トレースを作成するために、すべての関数をキャッチして再スローすることはお勧めできませんが、最初に例外がスローされたファイル名、行番号、および関数名を添付することをお勧めします。BOOST_THROW_EXCEPTION で boost::exception を使用すると、この動作を無料で取得できます。例外のデバッグと処理に役立つ説明情報を例外に添付することもお勧めします。とはいえ、これらはすべて、例外が構築された時点で発生するはずです。構築されたら、そのハンドラーに伝播できるようにする必要があります...厳密に必要以上に繰り返しキャッチして再スローしないでください。重要な情報を添付するために特定の関数をキャッチして再スローする必要がある場合は、それで問題ありません。
いいえ、それは非常に恐ろしいことであり、例外自体にコールスタックが必要な理由がわかりません-例外の理由、行番号、および最初の例外が発生したコードのファイル名で十分です。
そうは言っても、本当にスタック トレースが必要な場合は、例外スロー サイトでコール スタック情報を 1 回生成する必要があります。これを行う唯一の移植可能な方法はありませんが、http://stacktrace.sourceforge.net/のようなものを VC++ 用の同様のライブラリと組み合わせて使用することはそれほど難しくありません。
より適切な解決策の 1 つは、トレーサー マクロ/クラスを構築することです。したがって、各関数の先頭に、次のように記述します。
TRACE()
マクロは次のようになります。
Tracer t(__FUNCTION__);
クラス Tracer は、構築時に関数名をグローバル スタックに追加し、破棄時に自身を削除します。その後、そのスタックは常にロギングまたはデバッグに使用でき、メンテナンスははるかに簡単 (1 行) であり、例外のオーバーヘッドは発生しません。
実装の例には、 http://www.drdobbs.com/184405270 、 http://www.codeproject.com/KB/cpp/cmtrace.aspx、 http://www.codeguru.com/cpp/vsなどがあります/debug/tracing/article.php/c4429 . また、このhttp://www.linuxjournal.com/article/6391のような Linux 関数は、この Stack Overflow question: How to generate a stacktrace when my gcc C++ app crashesで説明されているように、よりネイティブに実行できます。ACE の ACE_Stack_Trace も一見の価値があるかもしれません。
とにかく、例外処理方法は粗雑で、柔軟性がなく、計算コストが高くなります。クラス構築/マクロ ソリューションははるかに高速であり、必要に応じてリリース ビルド用にコンパイルできます。
すべての問題に対する答えは、優れたデバッガーです。通常、Linuxの場合はhttp://www.gnu.org/software/gdb/、Windowsの場合はVisualStudioです。プログラムの任意の時点で、オンデマンドでスタックトレースを提供できます。
現在の方法は、実際のパフォーマンスとメンテナンスの頭痛の種です。デバッガーは、目標を達成するために考案されましたが、オーバーヘッドはありません。
きれいなスタック トレースを提供する素敵な小さなプロジェクトがあります。
このSO Questionを見てください。これはあなたが探しているものに近いかもしれません。クロスプラットフォームではありませんが、答えは gcc と Visual Studio のソリューションを提供します。
スタック トレース サポート用のもう 1 つのプロジェクト: ex_diag。マクロはなく、クロスプラットフォームが存在し、巨大なコードは必要ありません。ツールは高速で明確で使いやすいです。
ここでは、トレースが必要なラップ オブジェクトのみが必要であり、例外が発生した場合にトレースされます。
処理されない例外は、呼び出し元の関数が処理するために残されます。これは、例外が処理されるまで続きます。これは、関数呼び出しの前後で try/catch の有無にかかわらず発生します。つまり、try ブロックにない関数が呼び出された場合、その関数で発生した例外は自動的にコール スタックに渡されます。したがって、一番上の関数を try ブロックに入れ、catch ブロックで例外 "..." を処理するだけです。その例外はすべての例外をキャッチします。したがって、最上位の関数は次のようになります
int main()
{
try
{
top_most_func()
}
catch(...)
{
// handle all exceptions here
}
}
特定の例外に対して特定のコード ブロックが必要な場合は、それも可能です。それらが「...」例外キャッチブロックの前に発生することを確認してください。