组成
C++异常包括三部分:
- 引发异常:
throw
- 处理异常:
catch
- 使用try块:
try
异常提供了将控制权从程序的一个部分传递到另一部分的途径。
简单示例
#include <iostream>
using namespace std;
double sub(double z, double t);
int main(int argc, char *argv[])
{
double z, t, y;
while (cin >> z >> t)
{
// 可能引发异常的代码部分
try {
y = sub(z, t);
}
// 接收异常,由于下面的throw抛出
// 的是字符串,所以char* 来接收
catch (const char* s) {
cout << s << endl;
continue;
}
cout << "result: " << y << endl;
}
return 0;
}
double sub(double z, double t)
{
if (t == 0) {
// 抛出异常
throw "argument error";
}
return z / t;
}
被引发的异常可以是字符串或其他C++类型,通常为类类型。发生异常未匹配到处理程序,默认调用abort()。
对象用作异常类型
引发异常的函数将传递一个对象,优点:
- 使用不同的异常类型,区分不同的函数在不同的情况下引发的异常
- 对象可以携带信息
示例:
double sub(double z, double t);
// 定义一个异常类
class BadArgument
{
private:
double v1, v2;
public:
BadArgument() {}
void mesg();
};
inline void BadArgument::mesg()
{
cout << "argument error.";
}
int main(int argc, char **argv)
{
double z, t, y;
while (cin >> z >> t) {
try {
z = sub(z, t);
}
catch (BadArgument& ba) {
ba.mesg();
continue;
}
cout << "result: " << z << endl;
}
}
double sub(double z, double t)
{
if (t == 0) {
// 抛出异常
throw BadArgument();
}
return z / t;
}
异常规范
C++ 98新增的一项功能,C++ 11摒弃了。(不建议遵守此规范)
double harm(double a) throw(bad_thing); // may throw bad_thing exception
double marm(double) throw(); // does't throw an exception
throw()部分为异常规范,它可能出现在函数原型和函数定义中,可包含类型列表,也可不包含。
作用:
- 告诉用户可能需要try
- 让编译器添加执行运行阶段检查的码,检查是否违反了异常规范(很难实现)
关键字noexcept
指明函数不会引发异常:
double marm() noexcept;
栈解退
函数流程:程序将调用函数的指令的地址放到栈中,当被调用的函数执行完后,程序使用这个地址来确定从哪里开始继续执行。
异常流程:程序释放栈中内存,但不会在释放第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址,随后将控制权转到块尾部的异常处理程序。
异常机制将负责栈中的变量。
其他异常特性
引发异常时,编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的时引用。之所以在失去引用传递快的特点下仍要使用引用,为的是:基类引用可以执行派生类对象。
捕获全部异常:
try{
...
}
catch(...) { // 省略号的意思是捕获全部异常
...
}
catch语句使用基类对象时,将捕获所有的派生类对象,但派生类的特性将被剥夺。
class FException {
public:
void msg() {
cout << "This is f exception" << endl;
}
};
class SException: public FException {
public:
// 派生类重载方法
void msg() {
cout << "This is s exception" << endl;
}
};
void throw_exception()
{
throw SException(); // 抛出派生版本的异常
}
int main(int agrc, char ** argv)
{
try {
throw_exception();
}
// 用基类引用去捕获对象
catch(FException& se) {
se.msg();
}
return 0;
}
// 输出:
// This is f exception
但基类中使用虚函数可避免这种情况:
class FExecption {
...
virtual void msg() {...}
}
// 其他不需要改动
// 输出:
// This is s exception
exception类
使用:#include <exception>
exception可以直接使用,也可以作为基类使用。其中存在一个what()的虚拟成员函数,返回一个字符串。该字符串的特征随实现而异。
class MyExec: public exception {
public:
// what是基类的方法
char* what() { cout << "this is my exec" << endl; }
};
void throw_execption()
{
throw MyExec();
}
int main(int argc, char** argv)
{
try {
throw_execption();
}
catch(MyExec me) {
me.what();
}
return 0;
}
stdexception异常类
头文件stdexception定义了其他几个异常类。logic_error和runtime_error类都继承自exception。这两个类用作两个派生类系列的基类。
login_error描述典型的逻辑错误:
- domain_error:定义域异常,针对输入值不在预想的范围之中。
- invalid_argument:当函数传递了一个意料之外的值。
- length_error:当没有足够的空间来执行所需操作。
- out_of_bounds:用于指出所引错误。其中,可以重载
[]
(operator[]
)
runtime_error描述可能在运行期间发生但难以预计和方法的错误:
- range_error:计算结果不在函数允许范围之内,但没发生上溢出或下溢出。
- overflow_error:
- underflow_error:存在浮点类型可以表示最小非零值,计算结果比这个值还小时将导致下溢错误。
一般而言,login表示可以通过编程修复,runtime表示存在但无法避免。
bad_alloc
使用new导致的内存分配问题,c++最新处理方式是让new引发bad_alloc异常(也来自exception),需要引用头文件new,旧版的返回一个空指针。
int main(int argc, char** argv)
{
try {
int* f = new int[1000000000000000000000];
}
catch (bad_alloc& ba) {
cout << ba.what() << endl;
}
return 0;
}
// 输出:
// std::bad_array_new_length
异常、类和继承
就是在一个类中定义一个异常类。但是这样做的好处是什么,暂时不知道,留坑吧!
class Sales {
public:
...
class bad_index: public std::log_error {
...
}
}
异常分类
- 意外异常(unexcepted exception):如果异常在带异常规范的函数中引发,则必须与异常列表中的异常匹配,否则称之为意外异常。
- 未捕获异常(uncaught exception):如果异常不是在函数中引发的,或者是没有异常规范,这种异常必须捕获,否则称之为未补获异常。
未捕获异常不会导致程序立即终止,而是调用terminate()
,默认情况下这个函数会去调用abort()
,可以通过set_terminate()
修改。
void ignore_exception() // 未捕获异常处理方式
{
cout << "don's care exception" << endl;
exit(5);
}
void throw_exception()
{
throw "please stop my program";
}
int main(int argc, char** argv)
{
set_terminate(ignore_exception); // 更改默认的abort()
cout << "--one" << endl;
throw_exception();
cout << "--two" << endl;
return 0;
}
// 输出:
// --one
// don's care exception
对于意外异常,如果需要全部捕获,可以利用set_unexcepted()
转换:
void to_bad_exception()
{
throw std::bad_exception(); // or just throw
}
int main()
{
try {
...
}
catch (std::bad_exception& be) {
...
}
}
注意事项
虽然栈解退会自动释放变量或是调用对象的析构函数,但以下这种情况会导出内存泄漏:
void test2()
{
int* z = new int[10];
if(on) {
// 异常引发后会释放指针变量z,但不会释放z指向的内存
throw exception();
}
delete[] z;
}
可以这样处理:
void test3()
{
int* z = new int[10];
try {
if(on) {
throw exception();
}
}
catch(exception& exec) {
delete[] z; // 释放资源
...
}
delete[] z;
}
还不快抢沙发