网站首页 > 文章精选 正文
我们在编写应用软件时,不仅要保证软件的正确性,而且应该具有容错能力。也就是说,不仅在正确的环境条件下、在用户正确操作时要运行正确,而且在环境条件出现意外或用户使用操作不当的情况下,也应该有正确合理的表现,不能轻易出现死机,更不能出现灾难性的后果。由于环境条件和用户操作的正确性是没有百分之百保障的,所以我们在设计程序时,就要充分考虑到各种意外情况,并给予恰当的处理。这就是我们所说的异常处理。
程序运行中的有些错误是可以预料但不可避免的,例如内存空间不足、硬盘上的文件被移动、打印机未连接好等由系统运行环境造成的错误。这时要力争做到允许用户排除环境错误,继续运行程序;至少要给出适当的提示信息,提供更友好的交互。这就是异常处理程序的任务。
在一个大型软件中,由于函数之间有着明确的分工和复杂的调用关系,发现错误的函数往往不具备处理错误的能力。这时它就引发一个异常,希望它的调用者能够捕获这个异常并处理这个错误。如果调用者也不能处理这个错误,还可以继续传递给上级调用者去处理,这种传播会一直继续到异常被处理为止。如果程序始终没有处理这个异常,最终它会被传到C++ 运行系统那里,运行系统捕获异常后通常只是简单地终止这个程序。
C++ 的异常处理机制使得异常的引发和处理不必在同一函数中,这样底层的函数可以着重解决具体问题,而不必过多地考虑对异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛掷哪些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型,例如:
void fun() throw(A,B,C,D);
这表明函数fun()能够且只能够抛掷类型A 、B、C 、D及其子类型的异常。如果在函数的声明中没有包括异常接口声明,则此函数可以抛掷任何类型的异常,例如:
void fun();
一个不抛掷任何类型异常的函数可以进行如下形式的声明:
void fun () throw() ;
C++异常处理的真正能力,不仅在于它能够处理各种不同类型的异常,还在于它具有为异常抛掷前构造的所有局部对象自动调用析构函数的能力。
如果try块中发生的异常没有匹配的catch处理程序,或者不在try块中的语句发生异常,则包含该语句的函数会立即终止执行,试图在它的主调函数中查找包含异常的try块。该过程称为栈展开。catch语句如果匹配异常对象成功,在完成了对catch语句的参数的初始化(对传值参数完成了参数对象的copy构造)之后,对同层级的try块执行栈展开。
当特定作用域(scope)中抛出异常但是却没有捕获时(该scope内的throw没有try包裹,没有被catch,大部分情况是在主调函数中被catch),就会展开函数调用栈,试图在外层的try...catch块中捕获这个异常。函数调用栈展开意味着没有捕获该异常的函数终止执行,该函数内的所有局部变量被删除, 控制返回到最初调用该函数的语句。如果该函数调用语句包含在try块中,就会试图捕获该异常。如果该函数调用语句没有包含在try块中,则再次发生栈展开。如果一直没有可捕获该异常的catch处理程序, 就会调用terminate函数终止程序的执行。
// Demonstrating stack unwinding.
#include <iostream>
#include <stdexcept>
using namespace std;
// function3 throws runtime error
void function3() throw ( runtime_error )
{
cout << "In function 3" << endl;
// no try block, stack unwinding occurs, return control to function2(caller)
throw runtime_error( "runtime_error in function3" ); // no print
}
// function2 invokes function3
void function2() throw ( runtime_error )
{
cout << "function3 is called inside function2" << endl;
function3(); // stack unwinding occurs, return control to function1(caller)
}
// function1 invokes function2
void function1() throw ( runtime_error )
{
cout << "function2 is called inside function1" << endl;
function2(); // stack unwinding occurs, return control to main(caller)
}
// demonstrate stack unwinding
int main()
{
// invoke function1
try
{
cout << "function1 is called inside main" << endl;
function1(); // call function1 which throws runtime_error
}
catch ( runtime_error &error ) // handle runtime error
{
cout << "Exception occurred: " << error.what() << endl;
cout << "Exception handled in main" << endl;
}
}
main函数中,try块调用functionl。然后,function1调用funclion2,function2转而调用funclion3。function3抛出一个rumime_error对象。然而,由于该行的throw语句没有包含在try块中,因此发生栈展开,function3在throw处终止,然后将控制返回到function2中调用function的语句。由于该行没有包含在try块中,因此再次发生栈展开,function2在该行终止,并将控制返回到function1中调用function2的语句。由于该行没有包含在try块中,因此又一次发生栈展开,function1在函数调用处终止,并将控制返回到main函数中调用function1的语句。由于该语句包含在捕获并处理该异常。使用的what函数以显示异常信息。
某一scope内抛出异常,在该scope内因未被try包裹,未被catch,引发栈展开(stack unwinding),销毁局部对象,为局部对象调用析构函数:
#include <iostream>
#include <string>
using namespace std;
class MyException
{
public:
MyException(const string &message) : message(message) {}
~MyException() {}
const string &getMessage() const { return message; }
private:
string message;
};
class Demo {
public:
Demo() { cout << "Constructor of Demo" << endl; }
void print(){
cout<<"I am a Demo"<<endl;
}
~Demo() { cout << "Destructor of Demo" << endl; }
};
Demo* func() throw (MyException)
{
Demo d; // 如果在与d相同的作用域内抛出异常且未被catch,栈展开,被析构
Demo *p = new Demo;// // 如果在与d相同的作用域内抛出异常且未被catch,栈展开,不被析构
Demo c;// 抛// 如果在与d相同的作用域内抛出异常且未被catch,栈展开,被析构
cout << "Throw MyException in func()" << endl;
throw MyException("exception thrown by func()"); // 抛出异常,在该scope内因未被try包裹,未被catch,引发栈展开
return p;
//exit(1);// 析构函数不会被调用
}
int main()
{
cout << "In main function" << endl;
try{
Demo *p = func();// main调用func,由main处理异常,在func中引发栈展开
p->print();
free(p);
}
catch (MyException& e) {
cout << "Caught an exception: " << e.getMessage() << endl;
}
catch (char *str){
cout << str <<endl;
}
cout << "Resume the execution of main()" << endl;
getchar();
return 0;
}
/*
In main function
Constructor of Demo
Throw MyException in func()
Destructor of Demo
Caught an exception: exception thrown by func()
Resume the execution of main()
*/
栈展开(unwinding)是指当前的try...catch...块匹配成功或者匹配不成功异常对象后,从try块内异常对象的抛出位置(大部分情况是被调函数中),到try块的开始处的所有已经执行了各自构造函数的局部对象,按照构造生成顺序的逆序,依次被析构。如果当前函数内对抛出的异常对象匹配不成功,则从最外层的try语句到当前函数体的起始位置处的局部对象也依次被逆序析构,实现栈展开,然后再回退到调用栈的上一层函数内从函数调用点开始继续处理该异常。
由于线程执行时,被调用的函数的参数、返回地址、局部变量等都是依函数调用次序保存在函数调用栈(即线程运行时栈)上。当前被调用函数的参数、局部变量名字可以覆盖掉早前调用函数的同名变量,看起来就是只有当前函数内的名字可以访问,早前调用的函数内部的名字都不可访问,就像磁带被“卷起”。异常处理时按照函数调用顺序的逆序析构,依次析构各个被调函数的局部变量,就类似把已经卷起的“磁带”再展开,抹去上面记录的数据,故此“栈展开”得名。
-End-
猜你喜欢
- 2025-05-02 利用Axure+js创建可配置地图页面(axure做地图)
- 2025-05-02 JVM性能调优(1)——JVM内存模型和类加载运行机制
- 2025-05-02 C++变量作用域(c++变量的作用)
- 2025-05-02 成员变量、局部变量、静态变量(什么是成员变量和局部变量,以及它们的区别有哪些)
- 2025-05-02 C++_程序内存模型_内存四区_代码区_全局区
- 2025-05-02 「设计模式」10分钟学懂UML类图(uml类图设计模式有哪些)
- 2025-05-02 罗克韦尔(AB)PLC讲解,用户自定义指令(AOI)详解
- 2025-05-02 内部疯传,22个必考C++面试知识点总结(附答案解析)
- 2025-05-02 从零开始学习C语言丨全局和局部的作用域、变量
- 2025-05-02 嵌入式C语言基础编程—5年程序员给你讲函数,你真的懂函数吗?
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 稳压管的稳压区是工作在什么区 (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)