有一种用途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{ ... };
记住这expand
是 int[] 的 typedef,这意味着让我们计算一个整数数组。
0, (...)...;
表示第一个整数为零 - 请记住,在 c++ 中定义零长度数组是非法的。如果 args... 代表 0 个参数怎么办?这个 0 确保数组中至少有一个整数。
(ss << sep << args), sep = ", ", 0);
使用逗号运算符按顺序计算一系列表达式,取最后一个的结果。表达式是:
s << sep << args
- 将分隔符后跟当前参数打印到流中
sep = ", "
- 然后使分隔符指向逗号 + 空格
0
- 产生值 0。这是数组中的值。
(xxx params yyy)...
- 表示对参数包中的每个参数执行一次params
所以:
void (expand{ 0, ((ss << sep << args), sep = ", ", 0)... });
表示“对于 params 中的每个参数,在打印分隔符后将其打印到 ss。然后更新分隔符(以便我们为第一个分隔符使用不同的分隔符)。将所有这些作为初始化虚构数组的一部分,然后我们将抛出离开。