当前位置:网站首页>再谈exception——异常抛出时会发生什么?
再谈exception——异常抛出时会发生什么?
2022-06-28 13:21: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)
边栏推荐
- Mysql database literacy, do you really know what a database is
- Copy 10 for one article! The top conference papers published in South Korea were exposed to be plagiarized, and the first author was "original sin"?
- How does Quanzhi v853 chip switch sensors on Tina v85x platform?
- Stackoverflow 2022 database annual survey
- Pytorch main modules
- APP冷热启动专项测试
- Tencent tangdaosheng: facing the new world of digital and real integration, developers are the most important "architects"
- Fh511+tp4333 form an outdoor mobile power lighting camping lamp scheme.
- FH511+TP4333组成一个户外移动电源照明野营灯方案。
- Google Earth Engine(GEE)——联合国粮农组织全球有机土壤面积(1992-2018年度)
猜你喜欢

Manjaro easyconnecy error: libgtk-x11-2.0 so. 0: cannot open shared object file: No such file or directory

2. 01背包问题

移动Web实训-flex布局测试题1

全志V853芯片 如何在Tina V85x平台切换sensor?

5A synchronous rectifier chip 20V to 12v2a/5v4.5a high current 24W high power synchronous rectifier chip high current step-down IC fs2462

FS7022方案系列FS4059A双节两节锂电池串联充电IC和保护IC

Scratch travel photo album Electronic Society graphical programming scratch grade examination level 1 true questions and answers analysis June 2022

thinkphp6 多级控制器目录访问解决方法

1015. picking flowers

基于SSM实现水果蔬菜商城管理系统
随机推荐
Vscode如何设置代码保存后自动格式化
Google Earth engine (GEE) - Global organic soil area of FAO (1992-2018)
SHAREit实力出众,登陆全球 IAP 实力榜 Top7
中国广电5G套餐来了,比三大运营商低,却没预期那么低
How to set auto format after saving code in vscade
专业英语历年题
移动Web实训-flex布局测试题1
2.01 backpack problem
[today in history] June 28: musk was born; Microsoft launches office 365; The inventor of Chua's circuit was born
基于SSM实现水果蔬菜商城管理系统
My hematemesis collection integrates script teaching from various classic shell books. As Xiaobai, come quickly
Align content attribute in flex layout
Writing skills of resume
Centos7——安装mysql5.7
BUUCTF:[WUSTCTF2020]朴实无华
FH511+TP4333组成一个户外移动电源照明野营灯方案。
《天天数学》连载53:二月二十二日
弹性盒子自动换行小Demo
Shareit a une force exceptionnelle et se connecte au top 7 de la liste mondiale des forces IAP
哈希竞猜游戏系统开发技术成熟案例及源码