我有一个类,称之为
A
,其构造函数接受一些输入参数,如果它们与构造该对象不兼容,则可能会抛出异常。 在我的 main
代码中,我构造了一个 A
类型的对象,如下所示:
A my_obj(arg1,arg2,arg3);
并使用它。 显然,如果构造函数失败并抛出异常,程序的执行将在打印出“未处理的异常”消息后终止。
但是,在这种情况下,我想向用户提供更多信息,并告诉他/她为什么抛出
exception
。 所以,我需要一种方法来解决异常。 为此,一种可能性是将整个代码括起来,从
catch
的声明开始直到程序结束在
my_obj
块中,然后是 try
异常:catch
但这在我看来有点“矫枉过正”。 具体来说,因为在剩余的 100 行中没有抛出其他异常。 还有其他更好的方法来实现这一点吗?
try {
A my_obj(arg1, arg2, arg3);
// ...
// about 100 other lines of code being executed if my_obj is created properly
}
catch (std::exception& e) {
// print a user-friendly error message and exit
}
是一种类型,表示“我们这里可能没有对象”。
std::optional<>
然后
template <typename T, typename ... Args>
std::optional<T> try_make(Args&& ... args)
{ try {
return make_optional(std::forward(args...));
} catch (...) {
return {};
} }
,如下面的代码所示)。您可以将 auto my_obj = try_make<A>(arg1,arg2,arg3);
if (my_obj) {
// about 100 other lines of code being executed if my_obj is created properly
}
留空,在 try 块中调用构造函数,并将指针移动到
unique_ptr
中。之后您的其他代码就会执行。当然,您必须在简单的 if 语句中使用 unique_ptr
的 operator bool 检查有效指针。为了简化
unique_ptr
my_obj
。A& my_obj_ref = *my_obj;
请记住,这种方式会在堆上而不是堆栈上分配对象。
std::unique_ptr<A> my_obj;
try {
my_obj = std::move(std::unique_ptr<A>(new A(arg1, arg2, arg3));
}
catch (std::exception& e) {
// print a user-friendly error message and exit
}
if (my_obj) { // needed if your exception handling doesn't break out of the function
A& my_obj_ref = *my_obj;
// ...
// about 100 other lines of code being executed if my_obj is created properly
}
上面利用了这样一个事实:如果构建失败,您的程序将“退出”。如果要求继续运行,该函数可能会返回
template<typename... Args>
A make_a(Args&&... args) {
try {
return A(std::forward(args)...);
}
catch (std::exception& e) {
// print a user-friendly error message and exit
...
std::exit(EXIT_FAILURE);
}
}
// ... in the actual code:
A my_obj = make_a(arg1, arg2, arg3);
(如果您无法访问 C++17,则返回等效的 boost。)
这里有多种选择,具体取决于您希望在构建失败时如何继续控制。std::optional<A>
如果您想通过抛出不同的异常或在传播
A
构造异常之前执行某些操作来退出,则使用执行这些操作的工厂函数(可能是 lambda),例如:
A
如果你想通过返回值退出,那么你仍然可以使用上面的方法,但是将调用函数包装在另一个捕获预期异常并返回值的函数中。
或者您可以使用
auto a_factory(T x, U y) -> A // or use perfect forwarding
{
try { return A(x, y); }
catch(...) {
log("constructing A failed...");
throw other_exception();
}
}
// ...
A my_obj = a_factory(x, y);
(如下)或
optional
(如其他答案所涵盖)技术,但从
unique_ptr
块执行 return
语句。如果你想在没有有效
catch
的情况下继续执行,那么你可以这样做:A
如果您的函数中有多个对象需要考虑这一点,我倾向于建议将整个函数包装在可以处理所有不同异常的 try...catch 中的版本。
我倾向于这样做
简单std::optional<A> opt_my_obj;
try
{
A temp(...args...);
opt_my_obj.swap(temp);
} catch(...)
{
// handling, you could return from the function here
}
// At this point you can test `if ( opt_my_obj )` to branch the flow.
// When you're at a point where you have verified the object exists, you
// can enable normal object syntax by writing:
A& my_obj = *opt_my_obj;
中,如有必要,请截断并记住零终止符(我知道这可能会破坏堆栈,但如果我们不在递归函数中),然后抛出它。 示例:
std::array<char,4096>
优点:
快速为新类实现新的构造函数,因为只有一个
异常类,这适用于所有情况缺乏上下文:该消息必须显示类似“无法打开文件 foo:没有这样的文件或目录。”之类的内容。没有告诉用户异常的根本原因是什么。这个问题是从异常模型继承而来的,如果不将异常视为美化的错误代码就无法解决
如果您想在异常内容上进行分支,则必须解析消息,但我发现很少需要这样做。可能在编译器的上下文中,但无论如何都会打印该消息
try
{
Options opts(argv);
SomeResource resource(opts.someParameter());
//...More actions that could throw
}
catch(const std::array<char,4096>& errmessage) //Or rather some other type that contains the message.
{
fprintf(stderr,"Error: %s\n",errmessage.data());
return -1; //Or any non-zero value
}
return 0;
即
std::optional<FooThrow> foo;
try {
foo = std::make_optional<FooThrow>();
}
catch (...) {
std::cerr << "caught exception! could not construct FooThrow\n";
<return, exit, throw, whatever>
}
// 100 lines with foo
在这里就像动态初始化
一样工作,而不像
optional
那样在堆上动态分配内存。 Arthur O'Dwyer 在 CppCon 2020 上发表了一篇关于代数类型(元组、变体、并集、可选等)的精彩演讲。他提到了 new
的这个用例。一个最小的例子:
optional