新普京网站-澳门新普京 > 前端 > 中的分外处理机制

中的分外处理机制

2019/12/30 03:33

分外处理

抓实错误恢复生机工夫是增高代码强健性的最苍劲的门径之风姿罗曼蒂克,C语言中利用的错误管理方法被以为是紧耦合的,函数的使用者必得在十三分贴近函数调用的地点编写错误管理代码,那样会使得其变得呆滞和难以使用。C++中引入了老大管理体制,那是C++的第生龙活虎特色之生机勃勃,是思考难点和管理错误的生机勃勃种越来越好的点子。使用错误管理能够带给一些亮点,如下:

  • 错误管理代码的编制不再冗长没有味道,并且不再和健康的代码混合在同盟,程序员新普京网站 ,只必要编制希望发生的代码,然后在后面有个别单独的区段里编写管理错误的嗲吗。多次调用同叁个函数,则只需求有些地方编写一次错误管理代码。
  • 荒诞不能够被忽视,假如二个函数必得向调用者发送一次错误音讯。它将抛出贰个陈说那么些错误的目的。

规范十一分

C++标准库给大家提供了风度翩翩雨后鞭笋的正经八百十二分,那几个标准拾叁分都以从exception类派生而来,主要分为两大派生类,意气风发类是logic_error,另意气风发类则是runtime_error这四个类在stdexcept头文件中,前者首借使陈诉程序中冒出的逻辑错误,举个例子传递了没用的参数,后面一个指的是那八个不能预想的事件所形成的错误,例如硬件故障或内部存储器耗尽等,那二者都提供了三个参数类型为std::string的构造函数,那样就能够将十二分消息保存起来,然后通过what成员函数获得丰盛新闻.

#include <stdexcept>
#include <iostream>
#include <string>
using namespace std;

class MyError:public runtime_error {
public:
    MyError(const string& msg = "") : runtime_error(msg) {}

};

//runtime_error logic_error 两个都是继承自标准异常,带有string构造函数
//
int main()
{
    try {
        throw MyError("my message");   
    }   catch(MyError& x) {
        cout << x.what() << endl;    
    }
}

十二分的抓获

C++中通过catch关键字来捕获万分,捕获卓殊后能够对特别进行拍卖,这一个管理的语句块称为至极微处理机。上面是三个轻巧易行的抓获极度的事例:

    try{
        //do something
        throw string("this is exception");
    } catch(const string& e) {
        cout << "catch a exception " << e << endl;
    }

catch有一点像函数,能够有三个参数,throw抛出的十分对象,将会作为参数字传送递给相配到到catch,然后走入分外微机,下边包车型客车代码仅仅是显示了抛出风流倜傥种非常的事态,参加try语句块中有超大恐怕会抛出种种那么些的,那么该怎么管理呢,这里是能够接多个catch语句块的,那将招致引进此外三个标题,这正是什么开展匹配。

不行的抛出

class MyError {
    const char* const data;
public:
    MyError(const char* const msg = 0):data(msg)
    {
        //idle
    }
};

void do_error() {
    throw MyError("something bad happend");
}

int main()
{
    do_error();
}

澳门新普京 ,地方的例子中,通过throw抛出了一个那多少个类的实例,那几个丰硕类,能够是其他贰个自定义的类,通超过实际例化传入的参数能够表明发生的错误音讯。其实十二分就是三个含有特别新闻的类而已。极度被抛出后,需求被捕获,进而得以从破绽比很多中开展回复,那么接下去看看咋样去捕获三个特别吧。在上边这一个例子中应用抛出万分的秘籍来拓宽错误管理相比较与事前使用一些跳转的落到实处的话,最大的差别之处就是丰裕抛出的代码块中,对象会被析构,称之为仓库反解.

极度的相称

老大的合作自身觉着是顺应函数参数相配的条件的,然而又稍微不相同,函数相配的时候存在类型调换,然则丰硕则不然,在非常进程中不会做项目标转换,上边包车型大巴例证表明了那一个谜底:

#include <iostream>

using namespace std;
int main()
{
    try{

        throw 'a';
    }catch(int a) {
        cout << "int" << endl;
    }catch(char c) {
        cout << "char" << endl;
    }
}

上边的代码的输出结果是char,因为抛出的不行类型就是char,所以就合作到了首个特别微处理机。能够窥见在极渡进度中绝非发生类型的转变。将char调换为int。固然十一分微处理器不做类型转变,然则基类能够相称到派生类这几个在函数和那八个相配中都是实用的,但是急需在乎catch的形参需借使援用类型恐怕是指针类型,不然会 引致切割派生类这么些主题材料。

//基类
class Base{
    public:
        Base(string msg):m_msg(msg)
        {
        }
        virtual void what(){
            cout << m_msg << endl;
        }
    void test()
    {
        cout << "I am a CBase" << endl;
    }
    protected:
        string m_msg;
};
//派生类,重新实现了虚函数
class CBase : public Base
{
    public:
        CBase(string msg):Base(msg)
        {

        }
        void what()
        {
           cout << "CBase:" << m_msg << endl;
        }
};

int main()
{
    try {
        //do some thing
    //抛出派生类对象
        throw CBase("I am a CBase exception");

    }catch(Base& e) {  //使用基类可以接收
        e.what();
    }
}

上边的这段代码能够没有难题的行事,实际上大家见惯不惊编写自身的不得了管理函数的时候也是通过延续规范拾贰分来落到实处字节的自定义极度的,不过假若将Base&换到Base的话,将会促成对象被切割,比如上面这段代码将会编译出错,因为CBase被切割了,诱致CBase中的test函数不能够被调用。

    try {
        //do some thing
        throw CBase("I am a CBase exception");

    }catch(Base e) {
        e.test();
    }

到此为此,相当的合作算是说通晓了,总括一下,格外相称的时候差不离依照上面几条法则:

极度相配除了应当借使严谨的连串相称外,还协理下边多少个门类调换.

  • 同意特别量到常量的类型转变,也便是说能够抛出三个卓越量类型,然后使用catch捕捉对应的常量类型版本
  • 同意从派生类到基类的类型调换
  • 允许数组被撤换为数组指针,允许函数被转移为函数指针

假想后生可畏种状态,当自家要促成一代代码的时候,希望无论抛出什么样本种的百般小编都足以捕捉到,目前以来大家只能写上一大堆的catch语句捕获全数相当的大恐怕在代码中现身的老大来缓和那个难点,很醒目那样管理起来太过繁缛,幸而C++提供了后生可畏种可以捕捉任何特别的机制,可以运用下列代码中的语法。

   catch(...) {
    //异常处理器,这里可以捕捉任何异常,带来的问题就是无法或者异常信息
   }

只要您要落到实处二个函数库,你捕捉了你的函数库中的一些万分,不过你只是记录日志,并不去处理这个极其,管理非常的作业会交到上层调用的代码来管理.对于这么的一个场景C++也提供了扶助.

    try{
        throw Exception("I am a exception");    
    }catch(...) {
        //log the exception
        throw;
    }

经过在catch语句块中步向三个throw,就可以把当下抓获到的非常重新抛出.在十一分抛出的那焕发青新春中,笔者在代码中抛出了一个非常,然而小编未曾应用别的catch语句来捕获我抛出的那么些那些,试行下边包车型大巴次第会现身上面的结果.

terminate called after throwing an instance of 'MyError'
Aborted (core dumped)

干什么会现身如此的结果吗?,当我们抛出一个要命的时候,十分会趁着函数调用关系,拔尖一级升高抛出,直到被捕获才会终止,若是最后并未有被擒获将会促成调用terminate函数,上边包车型客车输出正是全自动调用terminate函数引致的,为了有限支撑更大的灵活性,C++提供了set_terminate函数能够用来安装自身的terminate函数.设置完结后,抛出的不得了如果未有被擒获就能够被自定义的terminate函数举行管理.上边是五个接收的例子:

#include <exception>
#include <iostream>
#include <cstdlib>
using namespace std;

class MyError {
    const char* const data;
public:
    MyError(const char* const msg = 0):data(msg)
    {
        //idle
    }
};

void do_error() {
    throw MyError("something bad happend");
}
//自定义的terminate函数,函数原型需要一致
void terminator()
{
    cout << "I'll be back" << endl;
    exit(0);
}

int main()
{
    //设置自定义的terminate,返回的是原有的terminate函数指针
    void (*old_terminate)() = set_terminate(terminator);
    do_error();
}
上面的代码会输出I'll be back

到此为此有关那二个相称的本身所了然的知识点都早就介绍完成了,那么随着可以看看下一个话题,格外中的能源清理.

特别规格表达

假设二个体系中应用了有的第三方的库,那么第三方库中的一些函数恐怕会抛出非常,但是大家不明了,那么C++提供了一个语法,将叁个函数恐怕会抛出的百般列出来,那样大家在编写代码的时候仿效函数的老大表达就可以,不过C++11中这中非常规格表达的方案已经被撤消了,所以自身不筹划过多介绍,通过三个例证看看其核心用法就能够,珍爱看看C++11中提供的可怜表达方案:

#include <exception>
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;

class Up{};
class Fit{};
void g();
//异常规格说明,f函数只能抛出Up 和Fit类型的异常
void f(int i)throw(Up,Fit) {
    switch(i) {
        case 1: throw Up();
        case 2: throw Fit();    
    }
    g();
}

void g() {throw 47;}

void my_ternminate() {
    cout << "I am a ternminate" << endl;
    exit(0);
}

void my_unexpected() {
    cout << "unexpected exception thrown" << endl;
 //   throw Up();
    throw 8;
    //如果在unexpected中继续抛出异常,抛出的是规格说明中的 则会被捕捉程序继续执行
    //如果抛出的异常不在异常规格说明中分两种情况
    //1.异常规格说明中有bad_exception ,那么会导致抛出一个bad_exception
    //2.异常规格说明中没有bad_exception 那么会导致程序调用ternminate函数
   // exit(0);
}

int main() {
 set_terminate(my_ternminate);
 set_unexpected(my_unexpected);
 for(int i = 1;i <=3;i++)
 {
     //当抛出的异常,并不是异常规格说明中的异常时
     //会导致最终调用系统的unexpected函数,通过set_unexpected可以
     //用来设置自己的unexpected汗函数
    try {
        f(i);    
    }catch(Up) {
        cout << "Up caught" << endl;    
    }catch(Fit) {
        cout << "Fit caught" << endl;    
    }catch(bad_exception) {
        cout << "bad exception" << endl;    
    }
 }
}

上边的代码表明了非常规格表明的基本语法,以至unexpected函数的作用,以至如何自定义自个儿的unexpected函数,还切磋了在unexpected函数中连续抛出相当的场合下,该怎么管理抛出的相当.C++11中撤消了这种非凡规格表达.引进了四个noexcept函数,用于评释那几个函数是还是不是会抛出非常

void recoup(int) noexecpt(true);  //recoup不会抛出异常
void recoup(int) noexecpt(false); //recoup可能会抛出异常

此外还提供了noexecpt用来检查实验叁个函数是或不是不抛出格外.

不行中的财富清理

在聊到有的跳转的时候,聊起一些调转不会调用对象的析构函数,会引致内部存款和储蓄器败露的主题材料,C++中的万分则不会有其大器晚成标题,C++中经过储藏室反解将早就定义的靶子进行析构,可是有贰个不如正是构造函数中只要现身了十二分,那么那会引致已经分配的财富无法回笼,下边是叁个结构函数抛出非常的例证:

#include <iostream>
#include <string>
using namespace std;

class base
{
    public:
        base()
        {
            cout << "I start to construct" << endl;
            if (count == 3) //构造第四个的时候抛出异常
                throw string("I am a error");
            count++;
        }

        ~base()
        {
            cout << "I will destruct " << endl;
        }
    private:
        static int count;
};

int base::count = 0;

int main()
{
        try{

            base test[5];

        } catch(...){

            cout << "catch some error" << endl;

        }
}
上面的代码输出结果是:
I start to construct
I start to construct
I start to construct
I start to construct
I will destruct 
I will destruct 
I will destruct 
catch some error

在地点的代码中布局函数发生了那多少个,诱致对应的析构函数未有奉行,因而实际编制程序过程中应当幸免在布局函数中抛出特别,若无主意制止,那么早晚要在布局函数中对其开展捕获实行管理.最终介绍二个知识点便是函数try语句块,假如main函数或然会抛出特别该怎么捕获?,如若结构函数中的开头化列表大概会抛出万分该怎么捕获?上边包车型的士多少个例子表明了函数try语句块的用法:

#include <iostream>

using namespace std;

int main() try {
    throw "main";
} catch(const char* msg) {
    cout << msg << endl;
    return 1;
}
main函数语句块,可以捕获main函数中抛出的异常.
class Base
{
    public:
        Base(int data,string str)try:m_int(data),m_string(str)//对初始化列表中可能会出现的异常也会进行捕捉
       {
            // some initialize opt
       }catch(const char* msg) {

            cout << "catch a exception" << msg << endl;
       }

    private:
        int m_int;
        string m_string;
};

int main()
{
    Base base(1,"zhangyifei");
}

地方说了超级多都以关于丰盛的行使,怎样定义自个儿的拾分,编写极度是或不是相应遵循一定的行业内部,在何地使用非常,至极是或不是平安等等一密密麻麻的标题,上边会相继探究的.

守旧的错误管理和这个管理

在探讨拾分管理在此之前,大家先谈谈C语言中的守旧错误管理方法,这里列举了之类二种:

  • 在函数中回到错误,函数会设置一个大局的荒诞状态标识。
  • 选用时域信号来做数字信号管理种类,在函数中raise时限信号,通过signal来安装信号管理函数,这种艺术耦合度超高,况兼不一样的库产生的功率信号值恐怕会发生冲突
  • 应用标准C库中的非局地跳转函数 setjmp和longjmp ,这里运用setjmp和longjmp来演示下如何开展错误管理:
#include <iostream>
#include <setjmp.h>
jmp_buf static_buf; //用来存放处理器上下文,用于跳转

void do_jmp()
{
    //do something,simetime occurs a little error
    //调用longjmp后,会载入static_buf的处理器信息,然后第二个参数作为返回点的setjmp这个函数的返回值
    longjmp(static_buf,10);//10是错误码,根据这个错误码来进行相应的处理
}

int main()
{
    int ret = 0;
    //将处理器信息保存到static_buf中,并返回0,相当于在这里做了一个标记,后面可以跳转过来
    if((ret = setjmp(static_buf)) == 0) {
        //要执行的代码
        do_jmp();
    } else {    //出现了错误
        if (ret == 10)
            std::cout << "a little error" << std::endl;
    }
}

错误管理方式看起来耦合度不是极高,符合规律代码和错误管理的代码抽离了,处理管理的代码都围拢在一齐了。可是依据这种局部跳转的办法来拍卖代码,在C++中却存在很严重的主题素材,那正是目的不可能被析构,局地跳转后不会积极性去调用已经实例化对象的析构函数。这将招致内存败露的主题材料。上边这一个例子丰裕体现了那点

#include <iostream>
#include <csetjmp>

using namespace std;

class base {
    public:
        base() {
            cout << "base construct func call" << endl;
        }
        ~base() {
            cout << "~base destruct func call" << endl;
        }
};

jmp_buf static_buf;

void test_base() {
    base b;
    //do something
    longjmp(static_buf,47);//进行了跳转,跳转后会发现b无法析构了
}

int main() {
    if(setjmp(static_buf) == 0) {
        cout << "deal with some thing" << endl;
        test_base();
    } else {
        cout << "catch a error" << endl;
    }
}

在上边这段代码中,唯有base类的布局函数会被调用,当longjmp产生了跳转后,b那些实例将不会被析构掉,但是试行流已经无法再次来到这里,b那个实例将不会被析构。这就是有些跳转用在C++中来管理错误的时候带来的后生可畏对难点,在C++中非常则不会有那个主题素材的留存。那么接下去看看怎么着定义贰个卓殊,以致哪些抛出多个十三分和破获格外吧.

非常安全

老大安全笔者感到是一个挺复杂的点,不光光要求得以达成函数的法力,还要保存函数不会在抛出极其的情况下,现身不均等的状态.这里举三个例证,大家在完毕货仓的时候时不经常来看书中的例子都是概念了三个top函数用来得到栈顶成分,还应该有三个重返值是void的pop函数仅仅只是把栈顶元素弹出,那么为何一向不一个pop函数能够 即弹出栈顶成分,况兼还能赢得栈顶成分呢?

template<typename T> T stack<T>::pop()
{
    if(count == 0)
        throw logic_error("stack underflow");
    else
        return data[--count];
}

假若函数在终极生龙活虎行抛出了三个十二分,那么那引致了函数未有将退栈的因素重回,但是Count已经减1了,所以函数希望获得的栈顶成分遗失了.本质原因是因为这几个函数试图二回做两件事,1.再次回到值,2.转移货仓的状态.最棒将那七个独立的动作放到多个单身的函数中,遵守内聚设计的基准,每叁个函数只做后生可畏件事.大家 再来钻探此外四个不行安全的标题,正是很见怪不怪的赋值操作符的写法,怎样确认保证赋值操作是丰裕安全的.

class Bitmap {...};
class Widget {
    ...
private:
    Bitmap *pb;

};
Widget& Widget::operator=(const Widget& rhs)
{
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

地点的代码不具有自己赋值安全性,如果rhs便是指标自己,那么将会引致*rhs.pb指向一个被剔除了的对象.那么就绪纠正下.插足证同性别测量检验.

Widget& Widget::operator=(const Widget& rhs)
{
    If(this == rhs) return *this; //证同性测试
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

而是未来地方的代码仍然不符合极度安全性,因为只要delete pb实施到位后在施行new Bitmap的时候现身了老大,则会引致最后指向一块被去除的内部存款和储蓄器.以往要是微微改良一下,就足以让位置的代码具有特别安全性.

Widget& Widget::operator=(const Widget& rhs)
{
    If(this == rhs) return *this; //证同性测试
    Bitmap *pOrig = pb;
    pb = new Bitmap(*rhs.pb); //现在这里即使发生了异常,也不会影响this指向的对象
    delete pOrig;
    return *this;   
}

其豆蔻梢头例子看起来照旧比较轻巧的,可是用途依旧不小的,对于赋值操作符来讲,比比较多动静都是要求重载的.

上一篇:没有了 下一篇:没有了