1. 简单异常的例子
1.1 成功捕获异常处理
异常的语法包括try{}块和catch{}块,try块用于检测异常的发生,catch块用于捕获异常。每个异常的抛出都使用了throw语句,并伴随了一个值(可以是任何类型)一起返回。例如throw 1.1,则是抛出一个浮点数数据类型的异常。当抛出异常后,程序会一直后退,直到找到被try块包围的函数,然后接着在try块后边找catch块,如果catch的括号参数中的类型和抛出的异常类型相同,则表示异常被捕获,并执行catch块中的代码。如果catch中没有与抛出的类型相同的参数,则表示异常未被捕获,这时程序将调用terminal()函数,异常终止程序。下边是一个简单的捕获异常的例子:
例1.1
-
#include <iostream>
-
using std::cout;
-
using std::endl;
-
-
void job()
-
{
-
cout << "job" << endl;
-
throw 1.1; //抛出浮点类型异常,抛出的异常是一个拷贝
-
//throw "ss"; //抛出字符串类型异常
-
//throw 5; //抛出整形字符串异常
-
}
-
-
int main()
-
{
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "1.1\n";
-
}
-
catch (char * s) {
-
cout << "1.2\n";
-
}
-
catch (...) {
-
cout << "1.3\n";
-
}
-
cout << "1.4\n";
-
return 0;
-
}
输出:
-
[root@192 ch-15]# ./main
-
job
-
1.3
-
1.4
注意事项:
1)catch(…)中的…表示捕获任意发生的异常。
2)try块和catch块的代码必须全部连续的写在一起,中间不能插入任何代码,否则编译不通过。
3)当异常被捕获之后,程序会接着被捕获的地方,向下继续执行代码,如例子中输出的1.4。
1.2 捕获异常失败
如果catch中没有与抛出的类型相同的参数,则表示异常未被捕获,这时程序将调用terminal()函数,而terminal()函数最终会调用abort()函数,异常退出进程。
例1.2
-
#include
-
using std::cout;
-
using std::endl;
-
-
void job()
-
{
-
cout << "job" << endl;
-
throw 1.1;
-
}
-
-
int main()
-
{
-
try {
-
job();
-
}
-
catch (char * s) {
-
cout << "2.1\n";
-
}
-
cout << "2.2\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
terminate called after throwing an instance of 'double'
-
Aborted (core dumped)
注意事项:
1)cout <<
"2.2\n"; 未被执行,原因在于程序调用abort()函数后将立即终止运行。
参考资料:
2. 重载terminate函数
2.1 重载terminate函数
当捕获异常失败时,调用的terminate()函数是可以重载的,方法是调用set_terminate()函数,该函数位于头文件中,且重载的函数必须是无参数无返回值的。重新设置terminate ()函数后,每次调用terminate ()函数时,就会调用重新设置的函数。
例2.1
-
#include <iostream>
-
#include <exception>
-
using std::cout;
-
using std::endl;
-
-
void deal_terminate ()
-
{
-
cout << "terminate" << endl;
-
std::abort(); //异常终止进程
-
}
-
-
void job()
-
{
-
cout << "job" << endl;
-
throw 1.1;
-
}
-
-
int main()
-
{
-
std::set_terminate(deal_terminate); //重载terminal()函数为deal_terminal()函数
-
try {
-
job();
-
}
-
catch (char * s) {
-
cout << "2.1\n";
-
}
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
terminate
-
Aborted (core dumped)
注意事项:
1) 如果多次调用set_terminate()函数,则最后那一次调用才是有效的。
2) 如果例2.1中deal_terminal()函数不调用abort()函数,将会发生什么呢?测试后发现,与以上输出结果相同,程序任然异常退出了。而且我重新在deal_terminal()函数中添加throw,抛出异常捕获任然不成功。这应该说明了,只要调用了terminate()函数,程序必然会被强制异常退出。
参考资料:
3. 异常规范
3.1 throw()和set_unexpected()
当throw定义在一个函数的参数后边,表示这个函数抛出的异常将受到限制。例如throw()表示这个函数不能抛出异常,若抛出了异常,则将调用unexpected()函数,而unexpected()函数会调用terminate()函数。和terminate()函数一样,unexpected()函数也可以被重载,设置重载的函数为set_unexpected()。但与terminate()函数不同的是,unexpected()的重载函数中可以重新抛出异常,有可能使得函数继续执行;而terminate()函数或其重载函数只要被调用,程序必然会异常终止。以下是使用throw()和set_unexpected()的一个例子。
例3.1
-
#include <iostream>
-
#include <exception>
-
using std::cout;
-
using std::endl;
-
-
void deal_terminate()
-
{
-
cout << "terminate" << endl;
-
std::abort();
-
}
-
-
void deal_unexpected()
-
{
-
cout << "unexpected" << endl;
-
throw 3.3;
-
}
-
-
void job() throw() //约定该函数不抛出异常
-
{
-
cout << "job" << endl;
-
throw 5;
-
}
-
-
int main()
-
{
-
std::set_unexpected(deal_unexpected); //重载unexpected()函数
-
std::set_terminate(deal_terminate);
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "3.1\n";
-
}
-
catch (...) {
-
cout << "3.2\n";
-
}
-
cout << "3.3\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
unexpected
-
terminate
-
Aborted (core dumped)
注意事项:
1)如果deal_unexpected()不抛出异常,则程序必然调用terminate()函数终止运行;反之,则有可能使程序继续运行。
参考资料:
3.2 noexcept
noexcept和throw()的用法大致相同,指明函数不引发异常。若函数引发了异常,则直接调用terminate()函数异常终止进程,而不是unexpected()函数。下边这个例子说明了这点。
例3.2
-
#include <iostream>
-
#include <exception>
-
using std::cout;
-
using std::endl;
-
-
void deal_terminate()
-
{
-
cout << "terminate" << endl;
-
std::abort();
-
}
-
-
void deal_unexpected()
-
{
-
cout << "unexpected" << endl;
-
throw 3;
-
}
-
-
void job() noexcept //noexcept
-
{
-
cout << "job" << endl;
-
throw 3.3;
-
}
-
-
int main()
-
{
-
std::set_unexpected(deal_unexpected);
-
std::set_terminate(deal_terminate);
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "3.1\n";
-
}
-
catch (std::bad_exception) {
-
cout << "3.2\n";
-
}
-
catch (...) {
-
cout << "3.3\n";
-
}
-
cout << "3.4\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
terminate //未调用unexpected()函数
-
Aborted (core dumped)
注意事项:
1)noexcept指定的函数出现异常,则直接调用terminate()函数异常终止程序。
3.3 throw(int, double)
如果在异常规范throw()中指明了类型,则表示该函数只接受该类型的异常抛出。若是其他异常,则同样调用unexcepted()函数。通常称未被指定抛出的异常未意外异常,以下是成功抛出一个异常的例子。
例3.3
-
#include <iostream>
-
#include <exception>
-
-
using std::cout;
-
using std::endl;
-
-
void deal_terminate()
-
{
-
cout << "terminate" << endl;
-
std::abort();
-
}
-
-
void deal_unexpected()
-
{
-
cout << "unexpected" << endl;
-
throw 3.3;
-
}
-
-
void job() throw(int, double) //约定该函数只抛出int和double类型异常
-
{
-
cout << "job" << endl;
-
throw 5;
-
}
-
-
int main()
-
{
-
std::set_unexpected(deal_unexpected); //重载unexpected()函数
-
std::set_terminate(deal_terminate);
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "3.1\n";
-
}
-
catch (...) {
-
cout << "3.2\n";
-
}
-
cout << "3.3\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
3.1
-
3.3
3.4 throw(bad_exception)
bad_exception类是由exception类派生的类,来自头文件中。如果异常规范throw中添加了bad_exception类型,又throw限制的函数抛出了意外异常,且set_unexpected()函数重载的unexpected()函数重新抛出了异常,则会得到以下两个效果。
1) 如果重载的unexpected()函数抛出的异常类型是throw中规定的类型,则抛出该类型异常。
2)如果重载的unexpected()函数抛出的异常类型不是throw中规定的类型,则抛出bad_exception类型异常。
例3.4.1和例3.4.2分别演示了这两种情况。
例3.4.1
-
#include <iostream>
-
#include <exception>
-
-
using std::cout;
-
using std::endl;
-
-
void deal_terminate()
-
{
-
cout << "terminate" << endl;
-
std::abort();
-
}
-
-
void deal_unexpected()
-
{
-
cout << "unexpected" << endl;
-
throw 3.3; //抛出不是job()函数规定的类型
-
}
-
-
void job() throw(int, std::bad_exception)
-
{
-
cout << "job" << endl;
-
throw 3.3; //意外异常
-
}
-
-
int main()
-
{
-
std::set_unexpected(deal_unexpected);
-
std::set_terminate(deal_terminate);
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "3.1\n";
-
}
-
catch (std::bad_exception) {
-
cout << "3.2\n";
-
}
-
catch (...) {
-
cout << "3.3\n";
-
}
-
cout << "3.4\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
unexpected
-
3.2 //抛出了bad_exception类型异常
-
3.4
例3.4.2
-
#include <iostream>
-
#include <exception>
-
-
using std::cout;
-
using std::endl;
-
-
void deal_terminate()
-
{
-
cout << "terminate" << endl;
-
std::abort();
-
}
-
-
void deal_unexpected()
-
{
-
cout << "unexpected" << endl;
-
throw 3; //抛出的是job()函数规定的类型
-
}
-
-
void job() throw(int, std::bad_exception)
-
{
-
cout << "job" << endl;
-
throw 3.3; //意外异常
-
}
-
-
int main()
-
{
-
std::set_unexpected(deal_unexpected);
-
std::set_terminate(deal_terminate);
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "3.1\n";
-
}
-
catch (std::bad_exception) {
-
cout << "3.2\n";
-
}
-
catch (...) {
-
cout << "3.3\n";
-
}
-
cout << "3.4\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
unexpected
-
3.1 //抛出了int类型异常
-
3.4
注意事项:
1)要成功处理意外异常必须满足两点,一是在throw中加入bad_exception规范,二是必须在重载的unexpected()函数中使用throw抛出异常。
2)如果unexpected()重载函数中,直接使用了throw;语句,并未指明抛出的数据类型(如throw 5),则抛出的数据和进入unexpected()重载函数前的异常数据相同。
参考资料:
4. exception类
4.1 logic_error和runtime_error
exception类是异常处理的基类,在头文件中,几乎所有处理异常的类都是由exception类派生出来的。logic_error类和runtime_error类就是由exception类派生出来的,在头文件中,它们分别派生出了domain_error, future_error, length_error, invalid_argument,
out_of_range类和overflow, range_error, system_error, underflow_error类。这些类只是名字不一样,内部实现相同。它们接收string对象作为参数,并使用what()方法返回c字符串。如果需要的话,也可以自己从exception派生出需要的异常类,不过构造函数和what()方法都需要自己实现。以下是使用logic_error和自己派生异常类的方法。
例4.1.1
-
#include <iostream>
-
#include <exception>
-
#include <stdexcept>
-
-
using std::cout;
-
using std::endl;
-
-
void job()
-
{
-
cout << "job" << endl;
-
throw std::out_of_range("out_of_range"); //使用out_of_range抛出异常
-
}
-
-
int main()
-
{
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "4.1\n";
-
}
-
catch (std::out_of_range & ou) {
-
cout << ou.what() << "4.2\n";
-
}
-
catch (std::logic_error & lo) {
-
cout << lo.what() << "4.3\n"; //打印从异常中返回的数据
-
}
-
catch (...) {
-
cout << "4.4\n";
-
}
-
cout << "4.5\n"; return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
out_of_range4.2 //捕获到out_of_range类异常
-
4.5
例4.1.2
-
#include <iostream>
-
#include <exception>
-
#include <stdexcept>
-
-
using std::cout;
-
using std::endl;
-
-
//自己派生的异常类
-
class my_exception : public std::exception
-
{
-
private:
-
std::string si;
-
public:
-
explicit my_exception(const std::string & s) : si(s){};
-
const char* what() const noexcept { return si.c_str(); }
-
};
-
-
void job()
-
{
-
cout << "job" << endl;
-
throw my_exception("my_exception"); //抛出异常
-
-
}
-
-
int main()
-
{
-
try {
-
job();
-
}
-
catch (int n) {
-
cout << "4.1\n";
-
}
-
catch (my_exception & ou) { //捕获异常
-
cout << ou.what() << "4.2\n";
-
}
-
catch (std::exception & ou) {
-
cout << ou.what() << "4.2\n";
-
}
-
catch (...) {
-
cout << "4.4\n";
-
}
-
cout << "4.5\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
job
-
my_exception4.2
-
4.5
注意事项:
1) 如果要自己从exception中派生出异常类,则需要自己重写构造函数和what()函数。
2) 捕获层次类的顺序是从离祖先类最远的类开始写catch块,最后一个类因该是祖先类(基类)。
参考资料:
5. new异常检测
5.1 bad_alloc
bad_alloc类直接由exception类派生出来,当new动态分配空间失败时,将抛出bad_alloc类型异常。具体看例5.1.1
例5.1.1
-
#include <iostream>
-
#include <exception>
-
#include <stdexcept>
-
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
try {
-
while (1) {
-
int * p = new int[9999]; //一直消耗内存
-
}
-
}
-
catch (std::bad_alloc & ba) {
-
cout << ba.what() << "5.1\n";
-
}
-
cout << "5.2\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
std::bad_alloc5.1 //捕获到bad_alloc异常
-
5.2
参考资料:
5.2 nothrow
c++提供了一种处理new的选项,即当动态分配空间失败时不抛出异常,而是返回空指针。这样的前提是在new后边添加上nothrow,请看下边一个例子。
例5.2.1
-
#include <iostream>
-
#include <exception>
-
#include <stdexcept>
-
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
try {
-
while (1) {
-
int * p = new (std::nothrow) int[9999]; //指定不抛出异常,只返回空指针
-
if (!p) {
-
cout << "5.0\n";
-
break;
-
}
-
}
-
}
-
catch (std::bad_alloc & ba) {
-
cout << ba.what() << "5.1\n";
-
}
-
cout << "5.2\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
5.0 //返回空指针
-
5.2
参考资料:
6. RTTI(运行阶段类型检测)
6.1 dynamic_cast
rtti是在动态运行代码时进行类型识别,只适用于包含虚函数的类。dynamic_cast是一个能使指向基类的指针转换成指向派生类的指针的运算符,就是向上类型转换。如果转换失败,则返回空指针。dynamic_cast除了实现指针间转换外,还可以实现引用之间的转换。不过当转换失败时,则是抛出bad_cast类型的异常,也是有exception类派生来,定义在头文件中>。以下是使用dynamic_cast进行指针和引用转换类的例子。
例6.1.1
-
#include <iostream>
-
#include <exception>
-
#include <stdexcept>
-
#include <typeinfo>
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
//class A { public: virtual ~A(){}}; //虚析构函数
-
class A { virtual void dump(){}}; //虚函数
-
class B : public A {};
-
-
//1.指针转换
-
A *p1 = new A;
-
A *p2 = new B;
-
-
B * np1 = dynamic_cast<B *>(p1);
-
if (np1) {
-
cout << "6.0\n";
-
}
-
-
B * np2 = dynamic_cast<B *>(p2);
-
if (np2) {
-
cout << "6.1\n";
-
}
-
-
//2.引用转换
-
A a1;
-
A & ya1 = a1;
-
try {
-
B & yb1 = dynamic_cast<B &>(ya1);
-
}
-
catch (std::bad_cast & ba) {
-
cout << ba.what() << endl;
-
}
-
cout << "6.2\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
6.1
-
std::bad_cast //引用转换失败,捕获异常
-
6.2
注意事项:
1) 要使dynamic_cast能转换成功,基类必须有虚函数,可以是虚析构函数,也可以是其它成员函数。因为dynamic_cast使用的条件必须是多态情况,即基类存在虚函数。
参考资料:
6.2 typeid
typeid可接受的参数为类名或结果为对象的表达式。typeid运算符返回type_info类型的引用,因此使用typeid(class).name(),可以得到类的字符串名称。由于type_info类型重载了==和!=运算符,所以可以使用这些运算符对类型进行比较。例如存在类A,定义了A *a = new A,这时可以通过typeid(A) == typeid(*a)是否相等来判断a是否指向的是A类型。若a是空指针,则会抛出bad_taypeid类型异常,bad_taypeid类同样派生于exception类。
下边的例子演示这几项特性。
例6.2.1
-
#include <iostream>
-
#include <exception>
-
#include <stdexcept>
-
#include <typeinfo>
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
class A { virtual void dump(){}};
-
class B : public A {};
-
-
A * p1 = new A;
-
A * p2 = new B;
-
-
if (typeid(A) == typeid(*p2)) { //比较是否是同一个类型
-
cout << "typeid(A) == typeid(*p2)\n";
-
}
-
-
if (typeid(B) == typeid(*p2)) {
-
cout << "typeid(B) == typeid(*p2)\n";
-
}
-
-
cout << typeid(*p2).name() << endl; //输出类型名称
-
-
A * p3 = NULL;
-
try {
-
typeid(*p3);
-
}
-
catch (std::bad_typeid & ba) { //捕获空类型异常
-
cout << ba.what() << endl;
-
}
-
-
cout << "6.0\n";
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
typeid(B) == typeid(*p2)
-
Z11summary_6_1vE1B //类型名称,链接符号
-
std::bad_typeid //类型指针为空时,捕获到异常
-
6.0
注意事项:
1) 不管类指针a本身是什么类型,typeid(*a)得到的类型始终是a指向的地址空间最开始定义时的类型。就好比上例中的A * p2 = new B,则typeid(*p2)的类型就应该是B类型,而不是A类型。
参考资料:
7. 类型转换运算符
7.1 const_cast
c++特有的类型转换运算符有dynamic_cast,
const_cast, static_cast, reinterpret_cast四种,dynamic_cast前边已经讲过了,这里说一下const_cast的作用。const_cast可以去除变量的const和volatile属性,且只能是指针或引用。
下边的例子演示了这种特性。
例7.1.1
-
#include <iostream>
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
const int a = 99;
-
int * b = const_cast<int *>(&a);
-
int & c = const_cast<int &>(a);
-
-
*b = 50;
-
cout << "a: " << a << endl;
-
cout << "b: " << *b << endl;
-
-
c = 40;
-
cout << "a: " << a << endl;
-
cout << "c: " << c << endl;
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
a: 99 //编译器优化导致cout直接输出a的值始终为99
-
b: 50
-
a: 99
-
c: 40
注意事项:
1) 以上例子存在的一个问题是,输出a变量时的值始终为初始化的值99,而它的引用和指针确实修改了a的值。原因应该是编译器的优化,当编译时,程序始终认为a是一个常量,所以将后边的所有a变量直接替换成了99。以上只是一种猜测,也可能存在其它原因。
参考资料:
7.2 static_cast
当两种类型A和B中的A能隐式的转换成B,但B不能隐式地转换成A时,这时可以用static_cast将B转换成A。就好比基类和派生类中,基类可以显示但不能隐式地转换成派生类一样,下边的例子说明了这点。
例7.2.1
-
#include <iostream>
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
class A { virtual void dump(){}};
-
class B : public A {};
-
-
A * p1 = new A;
-
B * p2 = static_cast<B *>(p1);
-
-
cout << p1 << endl;
-
cout << p2 << endl;
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
0x9583008
-
0x9583008
注意事项:
1) static_cast不能去掉const属性,即不能将带有const的类型转换成另一种类型。
参考资料:
7.3 reinterpret_cast
reinterpret的使用范围比较广,首先它可以实现任意两种指针类型之间的转换,即时是两个毫不相关的类型;其次也能将指针的值转换成整型值,只要该整型能存储指针的大小。
例7.3.1
-
#include <iostream>
-
using std::cout;
-
using std::endl;
-
-
int main()
-
{
-
//1.实现和static_cast相同功能
-
class A { virtual void dump(){}};
-
class B : public A {};
-
-
A * p1 = new A;
-
B * p2 = reinterpret_cast<B *>(p1);
-
B * p3 = static_cast<B *>(p1);
-
-
cout << "p1: " << p1 << endl;
-
cout << "p2: " << p2 << endl;
-
cout << "p3: " << p3 << endl;
-
-
//2.转换两个不同类型的指针
-
int * p4 = reinterpret_cast<int *>(p1);
-
cout << "p4: " << p4 << endl;
-
-
//3.将指针转换成整型值
-
int p5 = reinterpret_cast<int>(p1);
-
cout << "p5: " << std::hex << "0x" << p5 << endl;
-
return 0;
-
}
输出:
-
[root@192 summary]# ./main
-
p1: 0x9efd008
-
p2: 0x9efd008
-
p3: 0x9efd008
-
p4: 0x9efd008
-
p5: 0x9efd008
参考资料:
阅读(1454) | 评论(0) | 转发(0) |