C++ 异常处理
assert
有时需要在一些特定的地方主动报错以避免更大的问题,这时就需要使用 assert 断言
#include <cassert>
assert(condition);
如果 condition 为真,则什么都不做;如果 condition 为假,则停止程序
我们需要自己寻找程序中的异常,并进行分类,因此需要捕获异常
try
{
// 需要执行的语句
}
catch (错误类型 e1)
{
// 当出现错误 e1 时执行的语句
}
catch (错误类型 e2)
{
// 当出现错误 e2 时执行的语句
}
如果没有 catch 语句,出现异常会直接终止程序; C++ 允许定义多条 catch 语句,让每条 catch 语句分别对应一种可能的异常类型
如果我们不知道具体是什么样的异常,可以使用 ... 作为捕获参数
catch (...)
{
// 当出现错误时执行的语句
}
它可以捕获任何类型的异常。
throw
在程序中,可以用 throw 保留字来抛出一个异常,在某个 try 语句块里执行过 throw 语句,在它后面的所有语句(截止到这个 try 语句块末尾)将永远不会被执行
类型 函数名(参数) throw(数据类型);
例如我们可以定义抛出指定类型的异常
int check() throw (const char *)
{
throw "这里出现错误!";
}
如果没有使用这种语法来定义函数,就意味着函数可以抛出任意类型的异常。有些编译器不支持这种语法,则可以改为
int check()
{
throw "这里出现错误!";
}
下面展示一个可以抛出和捕获异常的例子
try
{
check(); // 抛出错误
throw "Hello";
}
catch (char * e)
{
cout << e << endl;
}
第一个 check() 已经抛出异常,之后的 throw 就不会执行了。
基本思想
传统的异常处理方式是通过函数返回值来判断,而 C++ 提供的机制使得异常的引发和异常的处理不必在同一个函数中

这样底层的函数就不必过多地考虑异常处理的问题,上层调用者可以在不同的位置设置不同的异常处理机制。
栈解旋
栈解旋是一种现象,当异常被抛出时,在此之前所有定义在栈上的变量会全部析构,析构顺序与构造顺序相反。
class A
{
public:
A()
{
cout << "A is created." << endl;
}
~A()
{
cout << "A is deleted." << endl;
}
};
void test()
{
try
{
A a1, a2;
// 抛出异常
throw 1;
}
catch (int e)
{
// 此时 a1 a2 已经析构
cerr << "Something wrong." << endl;
}
}
int main()
{
test();
system("pause");
return 0;
}
异常接口声明
为了增强可读性,可以在函数声明中添加关于异常抛出的信息
// 此函数可以抛出任何异常
void test()
{
// ...
}
// 此函数只能抛出 int, char, char* 异常
void test() throw(int, char, char*)
{
// ...
}
// 此函数不能抛出任何异常
void test() throw()
{
// ...
}
作用域
一般类型
对于一般类型, throw 的类型值赋值给异常抛出的变量
void test()
{
int x = 6;
throw x; // x 的值会赋给 e
}
void deal()
{
try
{
test();
}
catch (int e)
{
cerr << "Something wrong." << endl;
}
}
并且引用类型异常和一般类型异常等价
void deal()
{
try
{
test();
}
catch (int &e)
{
cerr << "Something wrong &." << endl;
}
catch (int e)
{
cerr << "Something wrong." << endl;
}
}
会按照先后顺序来 catch ,即如果引用在前,就会进入引用异常;如果一般类型在前,就会进入一般类型异常。
指针类型
抛出 char* 类型的异常时,如果值为常量,就可以直接获取
void test()
{
throw "abc"; // 因为常量存放在全局区,因此可以直接传递给 e
}
void deal()
{
try
{
test();
}
catch (char* e)
{
cerr << "Something wrong." << endl;
}
}
类异常
抛出自定义类的异常,此时需要显示调用构造函数抛出异常,并且会用拷贝构造函数创建新对象作为 e
void test()
{
throw A(); // 需要显式调用构造函数
}
void deal()
{
try
{
test();
}
// e 的值是通过拷贝构造函数获得的
catch (A e)
{
cerr << "Something wrong." << endl;
}
}
对于类对象,抛出引用类型的异常和非引用类型异常不能同时出现。这可能是因为引用类型异常会直接将抛出对象转化为 e 而不是用拷贝构造函数重新构造对象
void deal()
{
try
{
test();
}
// e 的值是匿名对象 A() 直接转化得到的
catch (A &e)
{
cerr << "Something wrong." << endl;
}
}
对于指针类型的异常,只能选择在抛出位置 new 一个新的对象,然后由 e 获取指针
void test()
{
throw new A; // 不用调用构造函数,因为 new 会自动调用
}
void deal()
{
try
{
test();
}
// e 获取 A 的地址
catch (A *e)
{
cerr << "Something wrong." << endl;
// 要销毁指针
delete e;
}
}
如果直接取 throw 变量的地址,由于作用域的问题,该对象会析构, e 会得到一个野指针
void test()
{
throw &(A()); // 直接返回地址的话,对象离开作用域就会析构
}
可以看出通过指针来抛出异常,就不得不在两个函数中分别 new 和 delete ,因此并不建议这样使用。
自定义异常类
class MyExcept
{
public:
MyExcept(string _type)
{
type = _type;
}
void what()
{
cout << type << endl;
}
private:
string type;
};
try
{
throw MyExcept("Hello");
}
catch (const MyExcept &e)
{
e.what();
}
异常的层次结构
实际应用中,通常是在需要进行异常处理的类中添加异常类来进行异常抛出,例如
class A
{
public:
A()
{
cout << "A is created." << endl;
}
// 定义类作为不同的类型
class eNegative
{
};
class eZero
{
};
};
void test()
{
try
{
// ... 抛出类型异常
throw A::eNegative();
}
catch (A::eNegative e)
{
// ...
}
catch (A::eZero e)
{
// ...
}
}
为了提供更加丰富的异常处理,以及简化处理过程,可以对异常类实行继承。
class A
{
public:
A()
{
cout << "A is created." << endl;
}
// 定义基类异常类
class eErr
{
public:
virtual void printErr()
{
cout << "eError" << endl;
}
};
class eNegative : public eErr
{
void printErr()
{
cout << "eNegative" << endl;
}
};
class eZero : public eErr
{
void printErr()
{
cout << "eZero" << endl;
}
};
};
void test()
{
try
{
// ...
throw A::eNegative();
}
catch (A::eErr &e)
{
e.printErr();
}
}
注意这里我们使用了虚函数用来实现多态,这样就可以在不同子类中设置不同的异常处理方式。由于多态特性,只需要通过基类对象的引用类型就可以 catch 所有子类的异常,再利用虚函数简化代码流程。
标准异常类
C++ 头文件 <stdexcept> 中定义了系统提供的异常类。它是一个 exception 类,然后派生出多种不同的异常类型。例如
class A
{
public:
void test()
{
try
{
throw out_of_range("超出范围");
}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
}
}
};
其中 out_of_range 就是 exception 类的派生类。利用多态,只需要一个分支来获取 exception 进行输出。
exception 类中有一个虚函数,用于输出错误信息,声明为:
virtual const char *what() const throw();
因此可以自定义派生的异常类
class TypeErr : public exception
{
public:
TypeErr(const char *p) : ep(p) {}
virtual const char *what()
{
cout << "类型错误" << endl;
return ep;
}
private:
char *ep;
}

浙公网安备 33010602011771号