当一个 C++ 程序在运行过程中遇到了异常情况,它可以通过抛出异常来通知上层代码进行异常处理。在此过程中,C++ 运行时会自动执行一些有序的操作步骤,以保证程序能够正确地处理异常。下面我们就来详细讲解一下这些操作步骤。
C++ 异常抛出和捕获机制
在 C++ 中,我们可以使用 throw
语句来抛出一份异常。其语法形式如下:
throw exception_type("exception message");
其中,exception_type
可以是任何类型,通常是基于 std::exception
或者其子类。"exception message"
则是用来说明异常原因的错误信息。值得注意的是,异常不是必须被抛出,这意味着你可以根据需要在任何时候选择抛出异常。
与抛出异常相对的是异常处理机制,也就是 try/catch
语句块。它的语法形式如下:
try {
// 执行可能抛出异常的语句
} catch (exception_type &exception) {
// 处理异常的语句,例如打印错误信息
}
在 try
中的代码块在执行过程中如果遇到异常,就会跳转到对应的 catch
中,进行异常处理操作。如果没有异常,则 catch
代码块就不会执行。
C++ 异常抛出后的执行顺序
当程序抛出异常后,C++ 运行时会自动执行以下操作步骤:
- 当程序在
try
块中执行发生异常时,它就会立即停止执行,跳过当前try
块中的所有语句。 - C++ 运行时向堆栈中的所有已经构造的对象发送一个清理信号,这意味着这些对象的析构函数将被调用。这些清理工作会先从栈顶开始依次执行,直到堆栈被清空为止。
- 如果没有合适的
catch
语句来处理异常,则程序将会结束运行,并且调用terminate()
函数来处理错误。如果有适当的catch
块,则控制流会跳转到catch
语句处理异常。
基于以上操作步骤,我们可以总结出 C++ 异常抛出的执行顺序:
try -> 抛出异常 -> 析构栈中的对象 -> 调用catch -> 继续执行后续程序
下面让我们来看两个具体的示例,以深入理解 C++ 异常抛出机制的执行顺序。
示例一:析构函数的调用顺序
#include <iostream>
class MyClass {
public:
MyClass(int value) : _value(value) {
std::cout << "Constructing MyClass instance with value: " << _value << std::endl;
}
~MyClass() {
std::cout << "Destructing MyClass instance with value: " << _value << std::endl;
}
int GetValue() const {
return _value;
}
private:
int _value;
};
int main() {
try {
MyClass obj1(1);
MyClass obj2(2);
MyClass obj3(3);
MyClass obj4(4);
throw "Exception"; // 抛出异常,程序会跳转到 catch 中进行处理
MyClass obj5(5); // 这行代码不会被执行
} catch (const char* ex) {
std::cout << "Exception caught: " << ex << std::endl;
}
return 0;
}
上述程序中定义了一个类 MyClass
,它具有一个构造函数和一个析构函数。其中构造函数只是简单地打印一句话来显示正在创建的实例,而析构函数则用于通知程序当前实例正在被销毁。
在 main()
函数中,我们分别创建了 4 个 MyClass
的实例,并通过 throw
语句抛出了一个异常。在执行此过程时,C++ 运行时会首先调用 MyClass
对象的析构函数进行清理工作。在上述代码中,我故意设置了抛出异常之前,已经构造了多个 MyClass
的实例,以演示析构函数的调用顺序。
当程序执行时,控制流先是进入 try
块,创建了 obj1
~ obj4
四个实例。然后,当执行到抛出异常的位置时,程序会立即停止执行,并进入 C++ 运行时的异常处理机制。在这个过程中,程序的堆栈中存在四个已经构造的 MyClass
对象,它们会依次执行析构函数,从而完成清理工作。
最后,C++ 运行时会调用 catch
块来处理异常,并输出一条错误信息。需要注意的是,catch
块并不会阻止程序继续执行,因此如果在 catch
块之后还有代码的话,这些代码仍然会被继续执行。
示例二:异常抛出和多级函数调用
#include <iostream>
class MyClass {
public:
MyClass(int value) : _value(value) {
std::cout << "Constructing MyClass instance with value: " << _value << std::endl;
}
~MyClass() {
std::cout << "Destructing MyClass instance with value: " << _value << std::endl;
}
int GetValue() const {
return _value;
}
private:
int _value;
};
void func2() {
MyClass obj2(2);
MyClass obj3(3);
throw "Func2 Exception"; // 抛出异常,程序会跳转到 func1 内部进行处理
MyClass obj4(4); // 这行代码不会被执行
}
void func1() {
MyClass obj1(1);
try {
func2();
} catch (const char* ex) {
std::cout << "Exception caught in func1: " << ex << std::endl;
throw;
}
MyClass obj5(5); // 这行代码不会被执行
}
int main() {
try {
func1();
} catch (const char* ex) {
std::cout << "Exception caught in main: " << ex << std::endl;
}
return 0;
}
在上述程序中,我们定义了三个函数:func1()
、func2()
和 main()
。其中 func1()
是一个多级函数调用的例子,它内部调用了 func2()
。这两个函数都有一个局部变量 obj1
和 obj2
,它们的构造函数也会输出一条简单的消息。
在 func2()
函数中,我们故意抛出了一个异常。这样会使得控制流立即跳转到 func1()
的 catch
块中进行异常处理。需要注意的是,在 catch
块内部我们选择了重新抛出异常,这样异常会继续向上(也就是 main()
函数)传递。这里我们用到了一个空 throw
语句,它的作用就是不改变当前异常,只是重新把异常抛出。
当程序执行时,控制流首先进入了 main()
函数。在 main()
函数中调用了 func1()
,而 func1()
又调用了 func2()
。当程序抛出了异常后,控制流会直接跳转到 func1()
函数内部的 catch
块,并输出一条错误信息。由于我们选择了重新抛出异常,因此异常会沿着 main()
-> func1()
-> main()
的调用链反弹回来,最终被 catch
块捕获并输出一条错误信息。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++ 程序抛出异常后执行顺序说明 - Python技术站