C++中的异常处理

1 C++中的异常处理基础

1.1 C++中异常处理简介

C++内置了异常处理的语法元素try…catch…:

  • try语句处理正常逻辑代码。
  • catch语句处理异常情况。
  • try语句中的异常由对应的catch语句处理。

在这里插入图片描述

C++中通过throw语句抛出异常:
在这里插入图片描述
C++异常处理分析:
throw抛出的异常必须被catch处理:

  • 当前函数能够处理异常,程序继续往下执行。
  • 当前函数无法处理异常,则函数停止执行,并返回。

在这里插入图片描述
编程实验:C++异常处理初探

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        throw 0;
    }
    
    return ret;
}

int main(int argc, char *argv[])
{    
    try
    {
        double r = divide(1, 0);
            
        cout << "r = " << r << endl;
    }
    catch(...)
    {
        cout << "Divided by zero..." << endl;
    }
    
    return 0;
}


1.2 异常类型匹配

同一个try语句可以跟上多个catch语句:

  • catch语句可以定义具体处理的异常类型。
  • 不同类型的异常由不同的catch语句负责处理。
  • try语句中可以抛出任何类型的异常。
  • catch(…)用于处理所有类型的异常,并且只能放在最后。
  • 任何异常都只能被捕获(catch)一次。

异常处理的匹配规则:
在这里插入图片描述
编程实验:异常类型匹配

#include <iostream>
#include <string>

using namespace std;

void Demo1()
{
    try
    {   
        throw 'c';
    }
    catch(char c)
    {
        cout << "catch(char c)" << endl;
    }
    catch(short c)
    {
        cout << "catch(short c)" << endl;
    }
    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[])
{    
    Demo1();
    
    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;
}


1.3 异常处理的另一种写法

异常处理还可以写成如下形式:

  • try…catch用于分割正常功能代码与异常处理代码。
  • try…catch可以直接将函数实现分割为2部分。
    在这里插入图片描述

1.4 异常被处理后局部对象的析构函数会被调用吗

看如下代码:

#include <iostream>
#include <string>

using namespace std;

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

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


void demo()
{
	Animal a;

	throw 0;
}


int main()
{
	try
	{
		demo();
	}
	catch (...)
	{
		cout << "...";
	}
	

	return 0;
}

输出结果如下:
在这里插入图片描述
但是如果不捕捉异常的话,就不会调用抛出异常前的局部对象的析构函数。


2 异常的二次抛出

2.1 catch中抛出基本类型异常

catch语句块中可以抛出异常:
在这里插入图片描述
catch中捕获的异常可以被重新解释后抛出,工程中使用这样的方式统一异常类型。

在这里插入图片描述
编程实验:异常的重新解释

#include <iostream>
#include <string>

using namespace std;

void Demo()
{
    try
    {
        try
        {
            throw 'c';
        }
        catch(int i)
        {
            cout << "Inner: catch(int i)" << endl;
            throw i;
        }
        catch(...)
        {
            cout << "Inner: catch(...)" << endl;
            throw;
        }
    }
    catch(...)
    {
        cout << "Outer: catch(...)" << endl;
    }
}


/*
    假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
    
    函数名: 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 "Invalid Parameter";
                break;
            case -2:
                throw "Runtime Exception";
                break;
            case -3:
                throw "Timeout Exception";
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    // Demo();
    
    try
    {
        MyFunc(11);
    }
    catch(const char* cs)
    {
        cout << "Exception Info: " << cs << endl;
    }
    
    return 0;
}


2.2 catch中抛出自定义类型异常

catch中可以抛出自定义类型异常:

  • 异常的类型可以是自定义类型。
  • 对于类类型异常的匹配仍旧是至上而下严格匹配的。
  • 赋值兼容性原则在异常匹配中依然适用。
  • 一般而言:
    • 匹配子类异常的catch放在上部。
    • 匹配父类异常的catch放在下部。

自定义异常类型的作用:

  • 在工程中会定义一系列的异常类。
  • 每个类代表工程中可能出现的一种异常类型。
  • 代码复用时可能需要重解释不同的异常类。
  • 在定义catch语句块时推荐使用引用作为参数。

编程实验:类类型的异常

#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;
}



3 标准库中的异常

C++标准库中提供了实用的异常类族:

  • 标准库中的异常都是从exception类派生的。
  • exception类有两个主要的分支:
    • logic_error:常用于程序中的可避免逻辑错误。
    • runtime_error:常用于程序中无法避免的恶性错误。

标准库中的异常:
在这里插入图片描述


4 terminate函数

先思考一个问题:如果在main函数抛出异常会发生什么?并且这个异常不进行处理,最后会传到哪里?
在这里插入图片描述

下面的代码输出什么?
在这里插入图片描述
输出“Test()”后程序运行结束,不同的平台输出的字符串不同,Windows平台会弹出程序异常的对话框。

异常的最终处理者:

  • 如果异常无法被处理,terminate()结束函数会被自动调用,结束整个程序。terminate()是整个程序释放系统资源的最后机会。
  • 默认情况下,terminate()调用库函数abort()终止程序。
  • abort()函数使得程序执行异常而立即退出。
  • C++支持替换默认的terminate()函数实现。

terminate()函数的替换:
自定义一个无返回值无参数的函数:

  • 不能抛出任何异常。
  • 必须以某种方式结束当前程序。

调用set_terminate(0设置自定义的结束函数:

  • 参数类型为void(*)()。
  • 返回值为默认的terminate()函数入口地址。

编程实验:自定义结束函数

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

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    exit(1);	//调用exit函数会确保所有的全局对象和静态局部对象的析构函数被调用,abort()函数不会
}

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;
}


5 函数的异常规格说明

思考一个问题:如何判断一个函数是否会抛出异常,以及抛出哪些异常?

函数的异常规格说明:

  • C++提供语法用于声明函数所抛出的异常。
  • 异常声明作为函数声明的修饰符,写在参数列表后面。

在这里插入图片描述
异常规格说明的意义:

  • 提示函数调用者必须做好异常处理的准备。
  • 提示函数的维护者不要抛出其他异常。
  • 异常规格说明是函数接口的一部分。
  • 函数声明和实现处都要写好上相同的规格声明。

问题:如果抛出的异常不在声明列表中中,会发生什么?
在这里插入图片描述
如上的代码,对于g++和bcc编译器,程序直接终止运行,而vs编译器则以char类型的异常捕获。

抛出的异常不在规格说明中:

  • 函数抛出的异常不在规格说明中,全局unexpected()函数被调用。
  • 默认的unexpected()函数会调用全局的terminate()函数。
  • 可以自定义函数替换默认的unexpected()函数实现。
  • 注意:不是所有的C++编译器都支持这个标准行为。

unexpected()函数的替换:
自定义一个无返回值无参数的函数:

  • 能够再次抛出异常:
    • 当异常符合触发函数的异常规格说明时,恢复程序执行。
    • 否则,调用全局terminate()函数结束程序。
  • 调用set_unecpected()设置自定义的异常函数:
    • 参数类型为void(*)()。
    • 返回值为默认的unexceptd()函数入口地址。

编程实验:自定义unexpected()函数

#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;
}

6 异常的生命周期

对于我们在各个地方抛出的异常,实际上都是抛出了一个匿名变量,不管是基本类型,还是自定义类型。这个匿名变量的生命周期出了cathc语句块就会结束。

对于如下几种形式:

int a = 0;
throw a;	// 实际上会生成一个匿名的临时变量

throw 1;	// 直接是匿名的临时变量

throw Exception();	// 生成匿名对象

Exception e;
throw e;	// 会生成匿名对象,对e进行拷贝

对于如下代码:

#include <iostream>

using namespace std;


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

	Exception(const Exception& obj)
	{
		cout << "Exception(const Exception& obj) : " << this << endl;
	}

	~Exception()
	{
		cout << "~Exception() : " << this << endl;
	}
};

void test36()
{
	int a = 123;

	try
	{
		throw Exception();
	}
	catch (const Exception& e)
	{
		cout << "&e : " << &e << endl;
	}
}

输出结果为:
在这里插入图片描述


参考资料:

  1. C++深度解析教程
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值