には 1 つの用途があり、その用途は 1 つstd::nested exception
だけです (私が発見できた限り)。
そうは言っても、素晴らしいことに、私はすべてのプログラムでネストされた例外を使用しています。その結果、あいまいなバグを探すのに費やす時間はほとんどゼロです。
これは、ネストされた例外により、エラーの時点で生成される完全に注釈が付けられたコール スタックを簡単に構築できるためです。実行時のオーバーヘッドはなく、再実行中に大量のログを記録する必要もありません (とにかくタイミングが変わります)。エラー処理でプログラム ロジックを汚染することもありません。
例えば:
#include <iostream>
#include <exception>
#include <stdexcept>
#include <sstream>
#include <string>
// this function will re-throw the current exception, nested inside a
// new one. If the std::current_exception is derived from logic_error,
// this function will throw a logic_error. Otherwise it will throw a
// runtime_error
// The message of the exception will be composed of the arguments
// context and the variadic arguments args... which may be empty.
// The current exception will be nested inside the new one
// @pre context and args... must support ostream operator <<
template<class Context, class...Args>
void rethrow(Context&& context, Args&&... args)
{
// build an error message
std::ostringstream ss;
ss << context;
auto sep = " : ";
using expand = int[];
void (expand{ 0, ((ss << sep << args), sep = ", ", 0)... });
// figure out what kind of exception is active
try {
std::rethrow_exception(std::current_exception());
}
catch(const std::invalid_argument& e) {
std::throw_with_nested(std::invalid_argument(ss.str()));
}
catch(const std::logic_error& e) {
std::throw_with_nested(std::logic_error(ss.str()));
}
// etc - default to a runtime_error
catch(...) {
std::throw_with_nested(std::runtime_error(ss.str()));
}
}
// unwrap nested exceptions, printing each nested exception to
// std::cerr
void print_exception (const std::exception& e, std::size_t depth = 0) {
std::cerr << "exception: " << std::string(depth, ' ') << e.what() << '\n';
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
print_exception(nested, depth + 1);
}
}
void really_inner(std::size_t s)
try // function try block
{
if (s > 6) {
throw std::invalid_argument("too long");
}
}
catch(...) {
rethrow(__func__); // rethrow the current exception nested inside a diagnostic
}
void inner(const std::string& s)
try
{
really_inner(s.size());
}
catch(...) {
rethrow(__func__, s); // rethrow the current exception nested inside a diagnostic
}
void outer(const std::string& s)
try
{
auto cpy = s;
cpy.append(s.begin(), s.end());
inner(cpy);
}
catch(...)
{
rethrow(__func__, s); // rethrow the current exception nested inside a diagnostic
}
int main()
{
try {
// program...
outer("xyz");
outer("abcd");
}
catch(std::exception& e)
{
// ... why did my program fail really?
print_exception(e);
}
return 0;
}
期待される出力:
exception: outer : abcd
exception: inner : abcdabcd
exception: really_inner
exception: too long
@Xenial の展開行の説明:
void (expand{ 0, ((ss << sep << args), sep = ", ", 0)... });
args はパラメーター パックです。0 個以上の引数を表します (ゼロが重要です)。
私たちがやろうとしていることは、コンパイラーに引数パックを拡張させ、その周りに有用なコードを作成させることです。
外から見てみましょう:
void(...)
- 何かを評価して結果を破棄することを意味しますが、評価は行います。
expand{ ... };
これは int[] の typedef であることを思い出してください。つまりexpand
、整数配列を評価してみましょう。
0, (...)...;
最初の整数が 0 であることを意味します。C++ では、長さ 0 の配列を定義することは違法であることを思い出してください。args... が 0 個のパラメーターを表す場合はどうなるでしょうか? この 0 により、配列に少なくとも 1 つの整数が含まれることが保証されます。
(ss << sep << args), sep = ", ", 0);
コンマ演算子を使用して一連の式を順番に評価し、最後の結果を取得します。式は次のとおりです。
s << sep << args
- 現在の引数に続いて区切り記号をストリームに出力します
sep = ", "
- 次に、区切り記号をカンマ + スペースにします
0
- 結果は値 0 になります。これが配列に入る値です。
(xxx params yyy)...
- これは、パラメーター パック内のパラメーターごとに 1 回実行することを意味しますparams
したがって:
void (expand{ 0, ((ss << sep << args), sep = ", ", 0)... });
「paramsのすべてのパラメーターについて、セパレーターを出力した後、それをssに出力します。次に、セパレーターを更新します(最初のセパレーターに別のセパレーターを持たせるため)。これらすべてを、次にスローする架空の配列の初期化の一部として行います。あちらへ。