提问人:Mikhail T. 提问时间:8/25/2023 最后编辑:Jarod42Mikhail T. 更新时间:8/26/2023 访问量:91
构造函数如何向调用方发出信号,它抛出的异常是否致命?
How can a constructor signal to caller, whether an exception it is throwing is fatal?
问:
我正在处理一个应用程序,它需要读取 10+ CSV 文件(不同类型的)作为输入。数据被读入容器 -- 或 .std::map
vector
以前,每种类型都有自己的解析函数,我正在努力将其统一为一个模板化函数:以节省未来的代码维护,并为损坏的文件提供统一的错误报告。
此函数读取每一行,辨别容器类型是否具有 (like ) 的概念,并用于这些和 (like )。key
map
emplace
emplace_back
vector
容器值类的唯一期望是其构造函数可以从 CSV 行实例化。构造函数的任何异常都是致命错误 -- 输入文件名和行号被报告,程序退出:
try {
if constexpr (is_associative_container<Container>(NULL)) {
result->emplace(typename Container::key_type(
key, keylen), value);
} else {
result->emplace_back(value);
}
} catch (const std::exception &e) {
fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
goto failure;
}
这一切都有效,我很高兴 - 大约75%的CSV解析现在由这个函数完成。
我现在面对的是剩下的四分之一的 CSV 文件,它们不那么简单:因为其中的某些行需要特殊处理,并且它们的内容不应该存储在容器中。
的构造函数如何向函数发出信号,即它抛出的异常不应被视为致命?一种方法是选择一个标准异常 (?) 作为信号,但这意味着,选择的异常不能意外发生——这是不可靠的......value_type
std::bad_function_call
别的东西?
答:
问题编辑与编写此答案重叠。原始答案如下。
value_type的构造函数如何向函数发出信号,表明它抛出的异常不应被视为致命?
请注意,在当前代码中,您已经区分了继承自的异常和不继承自 的异常。好的风格是继承所有的例外,但现实通常是不同的。std::exception
std::exception
std::exception
您可以引入一些特殊类型的异常来抛出:
try {
//...
} catch (const non_fatal_exception &e) {
// do something
} catch (...) { // all other exceptions are "fatal"
fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
goto failure;
}
关于动态异常规范的旧答案...
如注释中所述,动态异常规范已从 C++17 中删除。
在 C++17 之前,一个确实抛出异常规范中未列出的异常的函数执行了以下操作(来自 cppreference):
如果函数引发其异常规范中未列出的类型的异常,则调用该函数。默认函数调用 ,但它可能会被用户提供的函数 (via ) 替换,该函数可能会调用或抛出异常。如果异常规范接受抛出的异常,则堆栈展开将照常继续。如果不是,但异常规范允许,则引发。否则,将调用。
std::unexpected
std::terminate
std::set_unexpected
std::terminate
std::unexpected
std::bad_exception
std::bad_exception
std::terminate
除非您知道异常规范中可以接受的异常,否则没有出路,但通常您不知道这一点。我不知道在通用代码中推导出“允许”的异常。无论如何,该功能已被删除。从某种意义上说,抛出异常规范中未列出的异常已经是“致命的”,这不是您必须额外做的事情。
评论
e
e.what()
catch (...)
non_fatal_exception
const char *
如果行不应进入容器是正常的,那么这不是一个异常流,你不应该使用异常。该函数应采用另一个参数:Function<bool(RowData)> shouldInsertIntoContainer
评论
value_type
shouldInsertIntoContainer
好的,根据我自己的倾向——以及 @Eljay(评论)和 @463035818_is_not_an_ai(接受的答案)的建议,我这样修改了代码:
try {
if constexpr (is_associative_container<Container>(NULL)) {
result->emplace(typename Container::key_type(
key, keylen), value);
} else {
result->emplace_back(value);
}
} catch (const std::string &message) {
/*
* Allow constructors to fail without aborting the
* parsing of the file by throwing an std::string.
* If the string is not empty, output it to stderr.
*/
if (!message.empty())
fprintf(stderr, "%s:%zd: %s\n", path, line,
message.c_str());
continue;
} catch (const std::exception &e) {
fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
goto failure;
} catch (...) {
fprintf(stderr, "%s:%zd: internal error while "
"emplacing into %s\n",
path, line, typeid(Container).name());
goto failure;
}
评论
std::string
std::exception
e.what()
std::string
#include
RecoverableParsingError
评论
throw(...)
已从 C++ 中删除,并带有 C++17(C++ 为 20)。不再有类型化异常规范。函数可以潜在地将任何类型作为异常抛出,也可以根本不能引发。throw()
std::unexpected
std::terminate
std::set_unexpected
std::terminate
struct non_fatal_error : std::except { };
和。使用适当的 .struct fatal_error {};
throw