C++:如何捕获构造函数抛出的异常?

问题描述 投票:0回答:6

我有一个类,称之为

A
,其构造函数接受一些输入参数,如果它们与构造该对象不兼容,则可能会抛出异常。 在我的
main
代码中,我构造了一个
A
类型的对象,如下所示:

A my_obj(arg1,arg2,arg3);

并使用它。 显然,如果构造函数失败并抛出异常,程序的执行将在打印出“未处理的异常”消息后终止。

但是,在这种情况下,我想向用户提供更多信息,并告诉他/她为什么抛出

exception
。 所以,我需要一种方法来解决异常。

为此,一种可能性是将整个代码括起来,从

catch

的声明开始直到程序结束在

my_obj
块中,然后是
try
异常:

catch

但这在我看来有点“矫枉过正”。  具体来说,因为在剩余的 100 行中没有抛出其他异常。  还有其他更好的方法来实现这一点吗?

c++ exception
6个回答
3
投票
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 {}; } }



3
投票
unique_ptr

,如下面的代码所示)。您可以将 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;

请记住,这种方式会在堆上而不是堆栈上分配对象。


1
投票

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。)

这里有多种选择,具体取决于您希望在构建失败时如何继续控制。


0
投票
如果你想通过抛出异常的方式退出函数,那么你不需要做任何事情,你可以让

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 中的版本。
    

我倾向于这样做

简单

0
投票
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;
  •     

  • 我会按照@Caleth的建议选择工厂,但简单地“内联”它也可能是有意义的:

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

© www.soinside.com 2019 - 2024. All rights reserved.