C++查缺补漏之异常(续)

此文是文章《C++查缺补漏之异常》:http://blog.csdn.net/ii1245712564/article/details/44617881的后续

1.异常的重新抛出

可能单个catch不能完全处理一个异常,于是我们需要将这个其他具有更强处理能力的catch进行处理,那么这个异常就不止经过一个catch了,需要将异常重新抛出,抛出给其他的处理代码继续处理,这里用到的语句就是一个没有参数的throw语句。
try{
/** do something */
throw exception
}catch(exception & err)
{
/** deal the exception */
throw// rethrow the exception
}
注意事项:在异常重新抛出的时候,抛出的不是当前catch的异常形参,而是原本的异常对象,重新抛出的异常是由异常对象的动态类型决定的,而不是catch异常形参的静态类型决定的,就是catch的异常说明符是基类的,而异常对象是派生类的,那么重新抛出的异常对象是派生类的,而不是基类的!
要是不是在一个catch子句里面用throw语句会怎么样呢?那么程序将会调用terminate标准库函数,程序马上奔溃
下面来看一个例子:
#include <iostream>
using namespace std;

class A
{
public:
    A(int _number):number(_number){};
    int number;
};

void testFunc()
{
    try{
        throw A(10);
    }catch(A & err)
    {
        cout<<"In the testFunc the number is :"<<err.number<<endl;
        err.number =11;
        throw ;//rethrow the execption 
    }
}


int main(int argc, char const *argv[])
{
    try{
        testFunc();
    }catch(A & err)
    {
        cout<<"In the main Function the number is:"<<err.number<<endl;
    }
    while(1);
    return 0;
}
运行结果为:
"In the testFunc the number is  :10"
"In the main Function the number is:11"
这里的异常对象从testFunc里面抛出,在testFunc里面进行第一次处理以后,再重新抛出,在main里面进行第二次处理

2.捕获所有异常的处理代码

在我们的程序中,会发生各种各样的异常,我们用有限的catch语句来捕获不按规矩出现的异常,明显是不能完全覆盖的,于是就出现了一个叫可以捕获所有异常的catch子句:
try{
/** do something then throw exception */
}catch(...)
{
/** catch all kind exceptions */
}
就是上面的catch(...)语句可以捕获所有种类的异常,我们可以使用这种catch子句来为每种类型的异常做一些局部工作,再重新抛出
注意:catch(...)应该总是放在所有catch子句的最后,否则任何在它之后的catch子句都不能被激活.

3.函数测试块与构造函数

我们的异常可以发生在程序的任何地方,包括构造函数,构造函数里面的try,catch语句是无法捕获初始化列表里面的异常的,要是我们想捕获在初始化列表里面的异常,我们需要编写函数测试块,写法如下:
ClassName
try:{初始化列表}
{
/** 构造函数内部初始化工作 */
}catch(exception)
{
/** 异常的处理代码 */
}
下面举一个栗子:
#include <iostream>
#include <stdexcept>
using namespace std;


class A
{
public:
    A()
    try : ptr(new int[-1])
    {
        cout<<"constructing class A object"<<endl;
    }catch(bad_alloc & err)
    {
        cout<<"Get the bad_alloc exception "<<err.what()<<endl;
    }catch(...)
    {
        cout<<"Get teh unknown exception"<<endl;
    }
private:
    int * ptr;
};


int main(int argc, char const *argv[])
{
    A a;
    while(1);
    return 0;
}

在构造A类型的对象的时候,因为是new int[-1],那么一定会抛出异常,最后被第一个catch子句捕获,注意构造函数测试块是唯一检测构造函数初始化列表异常的方法。

4.异常说明

查看普通函数声明的时候,不能确定函数能抛出什么类型的异常,但是为了编写适当的catch子句,了解函数时候抛出异常以及抛出什么异常很有用,异常说明指定,如果函数抛出异常,那么被抛出的异常将会是包含在说明中的一种,或者其中的派生类

4.1定义异常说明

异常说明跟在函数的形参表之后,一个异常说明在关键字throw之后跟着一个由圆括号括住的异常类型列表
void recoup(int) throw(runtime_error);
这个声明指出,recoup是一个形参为int,返回值为void,只能抛出runtime_error,或者runtime_error派生异常的函数
void no_problem() throw();
这个说明指出,no_problem不抛出任何异常
void any_exception();
这个说明指出any_exception可以抛出任何异常。
注意:函数的声明和定义都要有相同的异常说明!

4.2违反异常说明

我们虽然限制了函数可以抛出那些异常,但是到底抛出什么异常是在程序运行中才能决定的,如果程序运行中抛出了异常说明列表之外的异常,那么就会调用标准库函数unexcepted。默认情况下unexcepted函数会调用terminate函数

4.3异常说明与成员函数

像非成员函数一样,成员函数的声明的异常说明是跟在函数形参表之后的,列入C++标准库bad_alloc类定义的为所有成员函数都有空异常说明
class bad_alloc : public exception
{
public:
bad_alloc() throw();
/** other functions */
};

4.4异常说明与虚函数

对于派生类虚函数的异常声明不必与基类虚函数的异常声明完全相同。派生类虚函数的异常声明必须要比基类虚函数的异常声明更加的严格,或者二者相同。
这个限制保证,在使用基类指针动态调用派生类的虚函数时,不会抛出基类虚函数异常声明之外的异常,不然只用基类接口这种设定就不实际了,还需要根据相应的派生类增加相应的异常处理。倒不如直接在基类里面全部包含。
下面看一个例子:
#include <iostream>
#include <stdexcept>

using namespace std;

class A
{
public:
    A(){}
    virtual void func() throw(bad_alloc , runtime_error)
    {
        cout<<"Hello World"<<endl;
    }

};

class B : public A
{
public:
    B(){}
    void func() throw(bad_alloc ,runtime_error , logic_error)
    {

    }
};

int main(int argc, char const *argv[])
{
    A a;
    B b;
    return 0;
}
上面的B派生于A,但是B中func函数抛出的异常种类却比A中多,我们编译一下:
C:\Users\Administrator\Desktop\exception.cc:21:10: error: looser throw specifier for 'virtual void B::func() throw (std::bad_alloc, std::runtime_error, std::logic_error)'
     void func() throw(bad_alloc ,runtime_error , logic_error)
          ^
C:\Users\Administrator\Desktop\exception.cc:10:18: error:   overriding 'virtual void A::func() throw (std::bad_alloc, std::runtime_error)'
     virtual void func() throw(bad_alloc , runtime_error)
发现报错了,上面写着B中func的更松的异常说明

要是把B的func改为这样呢
void func() throw(bad_alloc)
{/** something */ }
编译通过!

要是改成这样呢
void func() throw(logic_error)
{/** something */}
编译出错,跟第一个一样的错误

所以我们得出结论,基类虚函数的异常说明是对应派生类虚函数的异常说明的一个超集!

5.函数指针与异常说明

异常说明是函数类型的一部分,我们可以在函数指针的定义中加入异常说明:
void (*funcPtr)(int) throw(runtime_error);
在用另一个指针初始化函数异常声明指针的时候,或者将后者赋值给函数地址的时候,二者的异常说明不必相同,但是源指针的异常说明必须至少与目标指针一样严格!
下面我们来看一些例子
void func(int) throw(runtime_error);// declare a func

void (*funcPtr)(int) = func;// ok
void (*funcPtr)(int) throw(runtime error) = func;// ok
void (*funcPtr)(int) throw(runtime_error , logic_error) = func;// ok
void (*funcPtr)(int) throw() = func;//error
上面我们看到前三个指针的赋值都是可行的,最后一个赋值是不行的,因为funcPtr指针不能抛出任何异常,但是func能抛出一个runtime_error异常。我们假如这样是可行的,让一个不能抛出任何异常的函数指针指向一个可以抛出异常的函数,那个在把这个函数指针用在程序中,那到底这个函数抛不抛出异常呢,现在就行不通了



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值