异常,类和继承

一、异常,类和继承

  1. 异常,类和继承以三种方式相互关联。
  2. 首先,可以像标准c++库所做的那样,从一个异常类派生出另一个;
  3. 可以在类定义中嵌套异常类声明来组合异常,这种嵌套声明本身可被继承,还可用作基类。

实例:

//声明一个Sales类,它用于存储一个年份以及一个包含12个月的数据的数组。
//LabeledSales类时从Sales派生而来的,新增了一个用于存储数据标签的成员。
#ifndef SALES_H
#define SALES_H
#include<stdexcept>
#include<string>
using namespace std;
class Sales
{

private:
   double gross[MONTHS];//派生类能使用
    int year;
public:
    enum{MONTHS=12};//可能是静态常量

    class bad_index:public logic_error//客户类的catch可以使用这个类作为类型
    {
    private:
        int bi;
    public:
        explicit bad_index(int ix,const string & s ="Index error in Sales object\n");
        int bi_val()const{return bi;}
        virtual ~bad_index()throw(){}
    };
    explicit Sales(int yy=0);
    Sales(int yy,const double * gr,int n);
    virtual ~Sales(){}
    int Year()const{return year;}
    virtual double operator [](int i)const;
    virtual double & operator [](int i);
};

class LabeledSalese:public Sales
{
public:
    class nbad_index:public Sales::bad_index//nbad_index从logic_error派生而来
    {

private:
string lbl;
public:
    nbad_index(const string &lb,int ix,
          const string &s="Index error in LabeledSales object\n");
    const string & label_val()const{return lbl;}
    virtual ~nbad_index()throw(){}
};
explicit LabeledSalese(const string & lb="none",int yy=0);
LabeledSalese(const string &lb, int yy,const double * gr,int n);
virtual ~LabeledSalese(){}
const string & Label()const{return label;}
virtual double operator [](int i)const;
virtual double & operator [](int i);
private:
string label;
};
#endif // SALES_H
//下面的程序没有声明为内联的方法的实现,注意,对于被嵌套类的方法,需要使用多个作用域解析运算符。
//另外,如果数组索引超界,函数operator[]()将引发异常。
#include"sales.h"
#include<string>
using namespace std;
Sales::bad_index::bad_index(int ix, const string &s):logic_error(s),bi(ix)
{}
Sales::Sales(int yy)
{
    year=yy;
    for(int i=0;i<MONTHS;++i)
        gross[i]=0;
}
Sales::Sales(int yy, const double *gr, int n)
{
    year=yy;
    int lim=(n<MONTHS)?n:MONTHS;
    int i=0;
    for(i=0;i<lim;++i)
        gross[i]=gr[i];
    for( ;i<MONTHS;++i)
        gross[i]=0;
}
double Sales::operator [](int i)const
{
    if(i<0 || i>=MONTHS)
        throw bad_index(i);
    return gross[i];
}
double & Sales::operator [](int i)
{
    if(i<0||i>=MONTHS)
        throw bad_index(i);
    return gross[i];
}
LabeledSalese::nbad_index::nbad_index(const string &lb, int ix, const string &s):Sales::bad_index(ix,s)
{
    lbl=lb;
}
LabeledSalese::LabeledSalese(const string &lb, int yy):Sales(yy)
{
    label=lb;
}
LabeledSalese::LabeledSalese(const string &lb, int yy, const double *gr, int n):Sales(yy,gr,n)
{
    label=lb;
}

double LabeledSalese::operator [](int i)const
{
    if(i<0||i>=MONTHS)
        throw nbad_index(Label(),i);
    return Sales::operator [](i);
}

double & LabeledSalese::operator [](int i)
{
    if(i<0 || i>=MONTHS)
        throw nbad_index(Label(),i);
    return Sales::operator [](i);
}
//下面的程序中使用了这些类:首先试图超越LabeledSales对象sales2数组的末尾,然后试图超越Sales对象sales1中数组的末尾。
//这些尝试在两个try块中进行的,能够检查每中异常。
#include <iostream>
#include"sales.h"
using namespace std;
int main()
{
    double vals1[12]=
    {
        1220,1100,1122,2212,1232,2334,
        2884,2393,3302,2922,3002,3544
    };
    double vals2[12]=
    {
        12,11,22,21,32,34,
        28,29,33,29,32,35
    };
    Sales sales1(2011,vals1,12);
    LabeledSalese sales2("Blogstar",2012,vals2,12);
    cout<<"First try block:\n";
    try
    {
        int i;
        cout<<"Year="<<sales1.Year()<<endl;
        for(i=0;i<12;++i)
        {
            cout<<sales1[i]<<' ';
            if(i%6==5)
                cout<<endl;
        }
        cout<<"Year="<<sales2.Year()<<endl;
        cout<<"Label="<<sales2.Label()<<endl;
        for(i=0;i<=12;++i)
        {
            cout<<sales2[i]<<' ';
            if(i%6==5)
                cout<<endl;
        }
        cout<<"End of try block 1.\n";
    }
    catch(LabeledSalese::nbad_index & bad)
    {
        cout<<bad.what();
        cout<<"Company:"<<bad.label_val()<<endl;
        cout<<"bad index:"<<bad.bi_val()<<endl;
    }
    catch(Sales::bad_index & bad)
    {
        cout<<bad.what();
        cout<<"bad.index:"<<bad.bi_val()<<endl;
    }
    cout<<"\nNext try block:\n";
    try
    {
        sales2[2]=37.5;
        sales1[20]=23345;
        cout<<"End of try block 2.\n";
    }
    catch(LabeledSalese::nbad_index & bad)
    {
        cout<<bad.what();
        cout<<"Company:"<<bad.label_val()<<endl;
        cout<<"bad index:"<<bad.bi_val()<<endl;
    }
    catch(Sales::bad_index & bad)
    {
        cout<<bad.what();
        cout<<"bad index:"<<bad.bi_val()<<endl;
    }
    cout<<"done\n";
    return 0;
}

在这里插入图片描述

三、异常何时会迷失方向

异常被引发后,在两种情况下,会导致问题:

  1. 若在带异常规范的函数中引发的,必须与规范列表中的某种异常匹配(在继承乘次结构中,类类型与这个类及其派生类的对象匹配),否则称为意外异常。在默认情况下,这将导致程序异常终止(输入c++11摒弃了异常规范,但任支持)。
  2. 若异常不是在函数中引发的(或者函数没有异常规范),必须捕获它。如果没有捕获(在没有try块或没有匹配的catch块时,将出现这种情况),则异常被称为未捕获异常异常。在默认情况下,这将导致程序异常终止。然而可以修改程序对意外异常和未捕获异常的反应。
  3. 未捕获异常不会导致程序立刻异常终止。相反,程序将首先调用函数terminate()
  4. 在默认情况下,terminate()调用abort()函数。可以指定terminate()应调用的函数(而不是abort())来修改terminate()的这种行为,为此,可调用set_terminate()函数。

set_terminate()和terminate()都是在头文件exception中声明的:

typedef void (*terminate_handler)();//指向没有参数和返回值的函数的指针
terminate_handler set_terminate(terminate_handler f) throw();//c++98
terminate_handler set_terminate(terminate_handler f) noexcept;//c++11
void terminate();//c++98
void terminate() noexcept;//c++11
//set_terminate()函数将不带任何参数且返回类型为void 的函数的名称(地址)作为参数,并返回该函数的地址。
//调用了set_terminate()函数多次,则terminate()将调用最后一次set_terminate()调用设置的函数。

例:假设希望未捕获的异常导致程序打印一条消息,然后调用exit()函数,将退出状态值设置为5,包含头文件:exception。可以使用using编译指令,适当的using声明或std::限定符,来使其声明可用:

#include<exception>
using namespace std;

函数如下:

void myQuit()
{
cout<<"Terminating due to uncaught exception\n";
exit(5);
}

最后,在程序的开头,将终止操作作指定为调用该函数。

set_terminate(myQuit);

现在,如果引发了一个异常且没有被捕获,将调用terminate(),而后者将调用MyQuit(),意外异常。通过给函数指定异常规范,可以让函数的用户知道要捕获那些异常。假设函数的原型如下:

double Argh(double double) throw(out_of_bounds);

则可以这样使用该函数:

try{
x=Argh(a,b);
}
catch(out_of_bounds & ex)
{
...
}
  1. 指定应捕获那些异常很有帮助,因为默认情况下,未捕获的异常将导致程序异常终止。
  2. 异常规范应包含函数调用的其他含引发的异常。例如:如果Argh()调用了Duh()函数,后者引发retort对象异常,则Argh()和Duh()的异常规范中都应包含retort。除非自己编写所有的函数,否则无法保证正确的完成。

若函数引发了其异常规范中没有的异常,结果如何?

  1. 这时,行为与未捕获的异常及其类似,如果发生意外异常,程序将调用unexpected()函数。
  2. 这个含调用terminate(),后者在默认情况下将调用abort()

正如有一个可用于修改terminate()的行为的set_terminate()函数一样,也有修改unexpected()的行为的set_unexpected()函数。

这些函数必须在头文件:exception中:

typedef void (*unexpeced_handler)();
unexpected_handler set_unexpected(unexpected_handler f) throw();//c++98
unexpected_handler set_unexpected(unexpected_handler f) noexcept;//c++11
void unexpected();//c++98
void unexpected() noexcept;//c+0x

与提供给set_terminate() 的函数相比,提供给set_unexpected()的函数的行为受更严格的限制,具体说,unexpected_handler函数可以:

  1. 通过调用terminate()(默认行为),abort()或exit()来终止程序;

  2. 引发异常;引发异常(第二种选择)的结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数的异常规范。

  3. 如果新引发的异常与原来的异常规范匹配,则程序将从哪里开始进行正常处理,即寻找与新引发的异常匹配的catch块。基本上,这种方法将用预期的异常取代意外异常;

  4. 如果新引发的异常与原来的异常规范不匹配,且异常规范中没有包括bad_exception类型,则程序将调用terminate()bad_exception是从exception派生而来的,其声明位于头文件exception中。

  5. 如果新引发的异常与原来的异常规范不匹配,且原来的异常规范中包含了bad_exception类型,则不匹配的异常将被bad_exception异常所取代。

总之,要捕获所有的异常(不管是预期的异常还是意外异常),则可以这样做:

#include<exception>
using namespace std;
//然后,设计一个替代函数,将意外异常转换为bad_exception异常,该函数的原型如下:
void myUnexpected()
{
	throw bad_exception();
}

仅使用throw,而不是指定异常将重新引发原来的异常。然而,如果异常规范中包含了这种类型,则该异常将被bad_exception对象取代。

接下来在程序的开始位置,将意外异常操作指定为调用该函数:

set_unexpected(myUnexpected);

最后,将bad_exception类型包括在异常规范中,并添加如下catch块序列:

double Argh(double double) throw(out_of_bounds,bad_exception);
...
try{
x=Argh(a,b);
}
catch(out_of_bounds & ex)
{
...
}
catch(bad_exception & ex)
{
...
}

四、有关异常的注意事项

讨论如何使用异常:应在设计程序时加入异常处理功能,而不是以后再添加。但这样有缺点。例如:使用异常会增加程序代码,降低程序的运行速度。异常规范不适合用于模板,因为模板函数引发的异常可能随特定的具体化而异。异常和动态内存分配并非总能协同工作。
例:

void test1(int n)
{
string mesg("I'm trapped in an endless loop");
...
if(oh_no)
throw exception();
...
return ;
}

string 类采用动态内存分配。通常,当函数结束时,将mesg调用string的析构函数。虽然throw语句过早地终止了函数,但它任然使得析构函数被调用,这归功与栈解退。
例:

void test2(int n)
{
double * ar =new double[n];
...
if(oh_no)
throw exception();
...
delete [] ar;
return;
}

这时有个问题,栈解退时,将删除栈中的变量ar,但函数过早的终止意味着函数末尾的delete[]语句被忽略。指针消失了,但它指向的内存块未被释放,并且不可访问,总之,这些内存被泄漏了。

但可以避免。例如:可以在引发异常的函数中捕获该异常,在catch块包含一些清理代码,然后从新引发异常:

void test3(int n)
{
double * ar=new double [n];
...
try{
if(oh_no)
throw exception();
}
catch(exception & ex)
{
delete [] ar;
throw;
}
...
delete [] ar;
return;

然而,这将增加疏忽和产生其他错误机会。解决方法:这将用到智能指针模板。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值