当前位置:网站首页>再談exception——异常拋出時會發生什麼?
再談exception——异常拋出時會發生什麼?
2022-06-28 13:25:00 【GarryLau】
异常拋出時發生的事情跟return時發生的事情有點像
當某段代碼拋出一個异常時,會在堆棧中尋找catch處理程序,當發現一個catch的時候堆棧會釋放所有中間堆棧幀,並直接回到定義catch處理程序的堆棧層。堆棧釋放(stack unwinding)意味著所有具有局部作用域的名稱的析構函數都會被調用,然而當堆棧釋放的時候,並不釋放指針變量,也不會執行其它清理。
示例1:
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
namespace test_exception {
auto func2() -> void;
auto func1() -> void {
std::string str1;
std::string *str2 = new std::string();
func2();
delete str2;
}
auto func2() -> void {
std::ifstream in_file("test.txt");
throw std::exception();
in_file.close();
}
auto main() -> int {
std::cout << "testing exception..." << std::endl;
try {
func1();
}
catch(const std::exception& e) {
std::cerr << "Line " << __LINE__ << ", " << e.what() << std::endl;
return 1;
}
std::cout << "------------------------------" << std::endl;
return 0;
}
}
示例1輸出:
__cplusplus: 201703
testing exception...
Line 35, std::exception
The end.
當func2()拋出一個异常時,最近的异常處理程序在main()中,控制立刻從func2()的這一行:throw std::exception();跳轉到main()的這一行:std::cerr << "Line " << __LINE__ << ", " << e.what() << std::endl;。
在func2()中控制依然在拋出异常的那一行,後面的行永遠不會有機會運行:in_file.close();。然而幸運的是,因為in_file是堆棧中的局部變量,因此會調用ifstream析構函數,ifstream析構函數會自動關閉文件,因此在此不會泄漏資源。如果動態分配了in_file那麼這個指針不會被銷毀,文件也不會被關閉。
在func1()中,控制在func2()的調用中,因此後面的行永遠不會有機會執行:delete str2;,在此情况下確實會發生內存泄漏,堆棧釋放不會自動删除str2,然而str1會被正確地銷毀,因為str1是堆棧中的局部變量,堆棧會正確地銷毀所有局部變量。
備注: 可結合return語句理解。
示例2:
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
namespace test_exception {
auto func() -> void {
throw std::runtime_error("exception in func()");
}
auto testThrow1() -> auto {
std::ifstream in_file;
try {
std::string filename("testThrow.txt");
in_file.open(filename);
if(in_file.fail()) {
// 本示例假設文件打開成功,即不會走該if分支的异常
throw std::runtime_error(filename.c_str());
}
func();
}
catch(const std::runtime_error& e) {
std::cout << "Line " << __LINE__ << ", " << e.what() << std::endl;
}
catch(...) {
std::cout << "Line " << __LINE__ << ", some exception occurs!" << std::endl;
}
// 异常發生時執行上面catch語句塊,此句後面不會被執行,但因為in_file是局部變量,
// 因此會調用ifstream析構函數,ifstream析構函數會自動關閉文件,因此不會泄漏資源
// 如果in_file是動態分配的那麼這個指針不會被銷毀,文件也不會被關閉
in_file.close();
}
auto testThrow2() -> auto {
int *p;
try {
p = new int;
func();
}
catch(...) {
std::cout << "Line " << __LINE__ << ", some exception occurs!" << std::endl;
}
// 异常發生時執行上面catch語句塊,此句後面不會被執行,因此會產生內存泄漏
delete p;
}
auto main() -> int {
std::cout << "testing exception..." << std::endl;
testThrow1();
testThrow2();
std::cout << "------------------------------" << std::endl;
return 0;
}
}
示例2輸出:
__cplusplus: 201703
testing exception...
Line 24, testThrow.txt
Line 44, some exception occurs!
------------------------------
The end.
以上示例錶明粗心的异常處理會導致內存以及資源的泄漏。
在C++中可以通過使用智能指針或者捕獲、清理並重新拋出兩種技術來處理這種情况。
使用智能指針
智能指針對象在堆棧中分配,無論什麼時候銷毀智能指針對象,都會釋放底層的資源。
示例1中的func1()函數可改寫為:
#include <memory>
auto func1() -> void {
std::string str1;
std::unique_ptr<std::string> str2(new std::string("Hello"));
func2();
}
當從func1()返回或者拋出异常時,將自動删除std::string*類型的str2指針。
使用智能指針時,永遠不必考慮釋放底層的資源:智能指針的析構函數會自動完成這一操作,無論是正常退出函數還是拋出异常退出函數都是如此。
捕獲、清理並重新拋出
避免內存以及資源泄漏的另一種技術是使每個函數捕獲可能拋出的所有异常,執行必要的清理並且重新拋出异常供堆棧中更高層的函數處理。
示例1中的func1()可改為:
auto func1() -> void {
std::string str1;
std::string *str2 = new std::string();
try {
func2();
}
catch(...) {
delete str2;
throw; // rethrow the exception
}
delete str2;
}
該函數用异常處理程序封裝了func2()的調用,處理程序執行清理(删除了str2)並重新拋出异常。(本方案可以運行良好,但是繁瑣,需要兩行完全相同的代碼删除str2,一行用來處理异常,另一行在函數正常退出的情况下執行)。
關鍵字throw本身會重新拋出最近捕獲的任何异常。
使用智能指針是比捕獲、清理和重新拋出技術更好的解决方案。
Reference
1.Marc Gregoire, Nicholas A. Solter, Scott J. Kleper. C++高級編程(第2版). 清華大學出版社,2012.(P300)
边栏推荐
- Oceanwide micro fh511 single chip microcomputer IC scheme small household appliances LED lighting MCU screen printing fh511
- You must configure either the server or JDBC driver (via the ‘serverTimezone‘ configuration property
- 2.01 backpack problem
- Stm32f1 and stm32cubeide programming example - matrix keyboard driver
- How to display the server list of the electric donkey, and how to update the eMule server list
- Vscode如何设置自动保存代码
- c语言中的类结构体-点号
- 《天天数学》连载53:二月二十二日
- 泛海微FH511单片机IC方案小家电LED照明MCU丝印FH511
- 全志V853芯片 如何在Tina V85x平台切换sensor?
猜你喜欢

SHAREit實力出眾,登陸全球 IAP 實力榜 Top7

Stm32f1 and stm32cubeide programming example - matrix keyboard driver

Vscode如何设置代码保存后自动格式化

The counter attack story of Fu Jie, a young secondary school student: I spent 20 years from the second undergraduate to the ICLR outstanding Thesis Award

China Database Technology Conference (DTCC) specially invited experts from Kelan sundb database to share

scratch旅行相册 电子学会图形化编程scratch等级考试一级真题和答案解析2022年6月

1015.摘花生

嵌入式开发:估算电池寿命的7个技巧

腾讯汤道生:面向数实融合新世界,开发者是最重要的“建筑师”

中国广电5G套餐来了,比三大运营商低,却没预期那么低
随机推荐
895. longest ascending subsequence
From PDB source code to frame frame object
我呕血收集融合了来自各路经典shell书籍的脚本教学,作为小白的你快点来吧
MySQL multi table joint query
The English translation of heartless sword Zhu Xi's two impressions of reading
The difference between align items and align content
plt.savefig()的用法以及保存路径
Data analysis - promoter evolution analysis
FS7022方案系列FS4059A双节两节锂电池串联充电IC和保护IC
[机缘参悟-32]:鬼谷子-抵巇[xī]篇-面对危险与问题的五种态度
FH511+TP4333组成一个户外移动电源照明野营灯方案。
align-items 与 align-content 的区别
Electronic components distribution 1billion Club [easy to understand]
redis和mysql数据不一致问题如何解决?
移动Web实训DAY-2
Customize MySQL connection pool
Oracle cloud infrastructure extends distributed cloud services to provide organizations with greater flexibility and controllability
移动Web实训DAY-1
Fh511+tp4333 form an outdoor mobile power lighting camping lamp scheme.
认识启动函数,找到用户入口