gpt4 book ai didi

c++ - 为什么 C++ 不使用 std::nested_exception 来允许从析构函数中抛出?

转载 作者:IT老高 更新时间:2023-10-28 22:37:25 26 4
gpt4 key购买 nike

从析构函数抛出异常的主要问题是,在调用析构函数的那一刻,另一个异常可能“正在运行”(std::uncaught_exception() == true),所以它是在这种情况下该怎么做并不明显。用新异常“覆盖”旧异常将是处理这种情况的可能方法之一。但决定在这种情况下必须调用 std::terminate(或另一个 std::terminate_handler)。

C++11 通过 std::nested_exception 类引入了嵌套异常功能。此功能可用于解决上述问题。旧的(未捕获的)异常可以嵌套到新的异常中(反之亦然?),然后可以抛出嵌套的异常。但是这个想法没有被使用。 std::terminate 在 C++11 和 C++14 的这种情况下仍然被调用。

所以问题。是否考虑了嵌套异常的想法?有什么问题吗?在 C++17 中情况会不会有所改变?

最佳答案

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。然后更新分隔符(以便我们为第一个分隔符使用不同的分隔符)。将所有这些作为初始化我们想象的数组的一部分然后会扔掉。

关于c++ - 为什么 C++ 不使用 std::nested_exception 来允许从析构函数中抛出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37227300/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com