C/C++ 异常处理(二)

45 篇文章 1 订阅

1. try catch异常语法

1.1. tray catch与throw

C++内置异常处理语法try...catch...,try总是与catch一同出现,伴随一个try语句,至少应该有一个catch()语句。try内部正常应用逻辑,正常逻辑内部可能会有throw抛出异常。catch带有一个参数,参数类型以及参数名字都由程序指定,名字可以忽略,如果在catch随后的block中并不打算引用这个异常对象的话。参数类型可以是build-intype,例如int,long, char等,也可以是一个对象,一个对象指针或者引用。如果希望捕获任意类型的异常,可以使用“...”作为catch的参数。

throw后面带一个类型的实例,它和catch的关系就象是函数调用,catch指定形参,throw给出实参。编译器按照catch出现的顺序以及catch指定的参数类型确定一个异常应该由哪个catch来处理。throw不一定非要出现在try随后的block中,它可以出现在任何需要的地方,只要最终有catch可以捕获它即可。即使在catch随后的block中,仍然可以继续throw。这时候有两种情况,一是throw一个新类型的异常,这与普通的throw一样。二是要rethrow当前这个异常,在这种情况下,throw不带参数即可表达。例如:

#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;

void Demo1()
{
	double val = 1.0;
    try
    {
        throw 'c';
    }
    catch(char c)
    {
        cout << "catch(char c)" << endl;
        throw val;                            // 代码抛出新类型异常
    }
    catch(short c)
    {
        cout << "catch(short c)" << endl;
        throw;                                // 代码重新抛出当前的异常
    }
    catch(double c)
    {
        cout << "catch(double c)" << endl;
    }
    catch(...)
    {
        cout << "catch(...)" << endl;
    }
}

void Demo2()
{
    throw string("D.T.Software");
}

int main(int argc, char** argv) {

	try{
    	Demo1();
	}catch(double c){
		std::cout << "Catch throw double " << c << std::endl;
	}

    try
    {
        Demo2();
    }
    catch(char* s)
    {
        cout << "catch(char *s)" << endl;
    }
    catch(const char* cs)
    {
        cout << "catch(const char *cs)" << endl;
    }
    catch(string ss)
    {
        cout << "catch(string ss)" << endl;
    }

	return 0;
}

2.1. 异常处理匹配规则

catch异常匹配处理规则是严格从上到下优先匹配的,但catch不一定要全部捕获tryblock中抛出的异常,剩下没有捕获的可以交给上一级函数处理。当然还有一种情况是我们再catch语句中处理完内容以后再次调用throw抛出异常,这个抛出的异常只能在本方法调用的函数中进一步被捕捉到。什么情况下面会有这种异常重新抛出异常的情况呢?一个较大的项目必然会包含很多第三方的库文件,这个时候我们项目中为了异常的统一性,自然会进行一次异常的分装处理,这边是一种很好的异常捕获中抛出异常的情形。

#include <iostream>
#include <string>

using namespace std;

class Base
{
};

class Exception : public Base
{
    int m_id;
    string m_desc;
public:
    Exception(int id, string desc)
    {
        m_id = id;
        m_desc = desc;
    }

    int id() const
    {
        return m_id;
    }

    string description() const
    {
        return m_desc;
    }
};


/*
    假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码

    函数名: void func(int i)
    抛出异常的类型: int
                        -1 ==》 参数异常
                        -2 ==》 运行异常
                        -3 ==》 超时异常
*/
void func(int i)
{
    if( i < 0 )
    {
        throw -1;
    }

    if( i > 100 )
    {
        throw -2;
    }

    if( i == 11 )
    {
        throw -3;
    }

    cout << "Run func..." << endl;
}

void MyFunc(int i)
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
            case -1:
                throw Exception(-1, "Invalid Parameter");
                break;
            case -2:
                throw Exception(-2, "Runtime Exception");
                break;
            case -3:
                throw Exception(-3, "Timeout Exception");
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    try
    {
        MyFunc(11);
    }
    catch(const Exception& e)
    {
        cout << "Exception Info: " << endl;
        cout << "   ID: " << e.id() << endl;
        cout << "   Description: " << e.description() << endl;
    }
    catch(const Base& e)
    {
        cout << "catch(const Base& e)" << endl;
    }

    return 0;
}


2.4函数声明

函数声明异常这种方式在当前代码项目中很少被使用,其使用方法是还有一个地方与throw关键字有关,就是函数声明。例如:

void foo() throw (int);     //只能抛出int型异常
voidbar() throw ();         //不抛出任何异常
voidbaz();                 // 可以抛出任意类型的异常或者不抛出异常

如果一个函数的声明中带有throw限定符,则在函数体中也必须同样出现:

void foo() throw (int)
{
   ...
}

这里有一个问题,非常隐蔽,就是即使你象上面一样编写了foo()函数,指定它只能抛出int异常,而实际上它还是可能抛出其他类型的异常而不被编译器发现:

void foo() throw (int)
{
   throw float;     // 错误!异常类型错误!会被编译器指出
   ...
    baz();          // 正确!baz()可能抛出非int异常而编译器又不能发现!
}

voidbaz()
{
    throw float;
}

这种情况的直接后果就是如果baz()抛出了异常,而调用foo()的代码又严格遵守foo()的声明来编写,那么程序将abort()。这曾经让我很恼火,认为这种机制形同虚设,但是还是有些解决的办法,请参照“使用技巧”中相关的问题。

如果函数定义的异常和实际抛出的异常不一致的情况下,这个时候unexcepted函数函数会被自动调用。我们可以通过自定unexcepted函数进行这规划总异常类型函数的捕捉。

#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

void my_unexpected()
{
    cout << "void my_unexpected()" << endl;
    // exit(1);
    throw 1;
}

void func() throw(int)
{
    cout << "func()";
    cout << endl;

    throw 'c';
}

int main()
{
    set_unexpected(my_unexpected);

    try
    {
        func();
    }
    catch(int)
    {
        cout << "catch(int)";
        cout << endl;
    }
    catch(char)
    {
        cout << "catch(char)";
        cout << endl;
    }

    return 0;
}


1. 异常使用技巧

关于标注库中的异常类分析,详细见。

1.1 异常是如何工作的

为了可以有把握的使用异常,我们先来看看异常处理是如何工作的。

1.2. 异常的扩充

2. 异常注意

问题:如果异常不处理,最后会被传到哪里?可以通过实例确认各种编译器下面最终异常的处理是terminate()函数。terminal()函数是整个程序释放资源的最后机会,函数可以自定义这个函数,但是不能够在这个函数中再次抛出异常。

2.1. terminate()函数重构

首先,自定义一个无返回值无参数的函数,这个函数不抛出任何异常,同时一某种方式直接结束当前的应用程序。其次,调用set_terminate()设置自定义的结束函数,参数类型是void(*)(),返回值默认为terminate()函数入口地址。

#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    exit(1);
}

class Test
{
public:
    Test()
    {
        cout << "Test()";
        cout << endl;
    }

    ~Test()
    {
        cout << "~Test()";
        cout << endl;
    }
};


int main()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}

2.2 析构函数不能抛出异常

#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    exit(1);
}

class Test
{
public:
    Test()
    {
        cout << "Test()";
        cout << endl;
    }

    ~Test()
    {
        cout << "~Test()";
        cout << endl;

        throw 2;
    }
};


int main()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}

exit(1)被调用到的时候,再次出发对象的析构函数,在析构函数中又一次抛出异常,导致自定义的结束函数又一次被调用。如果在结束函数中释放资源,就会造成多次释放,容易造成崩溃。因此,我们将exit换成abort,这也是默认结束函数的行为,abort会直接结束程序。实际编程时不要在析构函数中扔出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值