[英]C++: How to catch exceptions thrown from constructors?
我有一個類,稱之為A
,其構造函數接受一些輸入參數,如果它們與構造該對象不兼容,則可能拋出異常。 在我的main
代碼中,我構造了一個類型為A
的對象,如下所示:
A my_obj(arg1,arg2,arg3);
並使用它。 顯然,如果構造函數失敗並拋出異常,則在打印出“未處理的異常”消息后將終止程序的執行。
但是,我想給用戶更多的信息,在這種情況下,告訴他/她為什么exception
已被拋出。 所以,我需要一種方法來catch
異常。
為此,一種可能性是將整個代碼括起來,從my_obj
的聲明my_obj
直到try
塊中的程序結束,然后catch
異常:
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
}
但這對我來說有點“矯枉過正”。 特別是因為在剩余的100行中沒有拋出其他異常。 有沒有其他更好的方法來實現這一目標?
如果構造函數拋出,則表示沒有對象。 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
}
一種可能性是使用指針(更好地使用智能指針,如下面的代碼中的unique_ptr )。 您可以將unique_ptr
留空,在try塊中調用構造函數並將指針移動到unique_ptr
。 之后,您的其他代碼將執行。 當然,您必須在簡單的if語句中檢查帶有unique_ptr
的運算符bool的有效指針。
為了簡化my_obj
的使用, 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);
以上內容利用了如果構造失敗則程序正在退出的事實。 如果要求繼續運行,則該函數可以返回std::optional<A>
(或者如果您無法訪問C ++ 17,則返回其等效增強值。)
這里有幾個選項,具體取決於如果構造失敗,您希望控制如何繼續。
如果要通過拋出異常退出函數,則不需要執行任何操作,可以讓A
構造異常傳播。
如果要通過拋出不同的異常退出,或者在讓A
構造異常傳播之前執行某些操作,則使用執行這些操作的工廠函數(可能是lambda),例如:
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
(由其他答案涵蓋)技術,但是從catch
塊執行return
語句。
如果要在沒有有效A
情況下繼續執行,則可以執行以下操作:
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;
如果你的函數中有幾個需要考慮的對象,我傾向於建議將整個函數包裝在try ... catch中的版本可以處理所有不同的異常。
我傾向於做簡單 :一次性人類可讀的消息。 當沒有選擇時,這種策略很有效,而且通常沒有。 雖然有一個問題,你希望異常處理相當健壯,所以我將消息打包在std::array<char,4096>
如果需要截斷並記住零終止符(我知道這可能會破壞堆棧但是如果我們不在一個遞歸函數中它應該沒問題,並拋出它。
例:
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;
優點:
缺點:
缺乏上下文:消息必須說“不可能打開文件foo:沒有這樣的文件或目錄。”。 不告訴用戶異常的根本原因。 此問題繼承自異常模型,如果不將異常視為美化錯誤代碼,則無法解決此問題
如果要對異常內容進行分支,則必須解析該消息,但我發現這很少需要。 可能在編譯器的上下文中,但無論如何都會打印該消息foo:54:1: Error: bar is not a baz
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.