当前位置:   article > 正文

C++异常处理机制

c++异常处理机制

一、异常的概念

C++的异常是用来处理错误的一种机制,在C语言中如果程序出现错误,我们通常使用比较多的方法是:使用assert终止程序和返回错误码。

比如在出现越界访问的时候、出现除零错误的时候,我们一般用assert强制终止程序。

再比如C语言中很多系统调用的库函数接口,如果调用失败,它们会把错误码放到errno中返回,这种做法其实比较麻烦,因为出现错误的时候我们还要拿着错误码去对照具体的错误是什么。

C++保留了C语言处理错误的机制,同时还新增了异常机制来处理错误。

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时,它可以抛出异常,让这个函数的直接或间接调用者处理这个异常

二、异常的使用

异常的使用语法是三个关键字:throwtrycatch

  • throw:当问题出现时,程序会抛出一个异常,这个动作可以通过throw关键字来实现。
  • try:try代码块中放置着可能会抛出异常的代码,这些代码称为保护代码。
  • catch:catch关键字用于捕获异常,catch代码块中放置着捕获异常后处理异常的代码,可以有多个catch进行捕获异常。
void func()
{
    if(......)
    {
        throw ......
    }
    else
    {
        ......
    }
}

try
{
    func();
}
catch(const std::exception& e)
{
    std::cerr << e.what() << '\n';
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们可以举一个具体的例子来演示一下异常的使用:我们模拟一个除零操作之后抛出异常并且捕获异常。

#include <iostream>

using namespace std;

int func(int a, int b)
{
    if(b == 0)
    {
        throw "除零错误";
    }
    else
    {
        return a / b;
    }
}

int main()
{
    try
    {
        func(1, 0);
    }
    catch(const char* errmsg)
    {
        cout << errmsg << endl;
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

throw关键字后面跟的是异常对象,异常是通过抛出对象而引发的,在catch语句块中会有不同异常对象的捕获,抛出的是什么对象类型,就决定了激活哪一个catch语句块的处理代码。

那么又会有一个问题:如果我们写了很多函数,每个函数都抛异常,并且抛出的异常对象都是不同的对象类型,那么多的对象类型我们都要捕获的话,岂不是要逐条逐条填写每个类型对应的catch语句块?这样未免效率太低了。

我们可以有两种方法来解决这个问题:

首先我们可以用catch(...)来捕获任意类型的异常,但这种方式有一个弊端,我们虽然捕获了所有的异常,但并不知道捕获到的具体是哪一个异常,不知道具体的异常错误是什么。

还有另一个办法,我们可以写一个自定义类型的异常,让它去继承exception异常类,或者再自定义一个异常类作为父类,最后我们只需要捕获父类异常即可。

三、异常类体系

1.C++标准库的异常类体系

C++标准库提供了异常类体系,它的父类是exception异常类,所有C++提供的标准异常都是它的子类。

在这里插入图片描述

但其实C++标准库的异常类体系我们使用的并不多,往往一些容器越界、动态申请内存失败会抛异常,由于这些异常都是继承了exception类的,所以我们在捕获异常的时候捕获父类对象即可。

#include <vector>
#include <iostream>

using namespace std;

int main()
{
    try
    {
        vector<int> v(10, 5);
        // 这里如果系统内存不够也会抛异常
        v.reserve(1000000000);
        // 这里越界会抛异常
        v.at(10) = 100;
    }
    catch (const exception &e) // 这里捕获父类对象就可以
    {
        cout << e.what() << endl;
    }
    catch (...)
    {
        cout << "Unkown Exception" << endl;
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2.自定义异常体系

实际在开发中我们更多地是自定义自己的异常体系进行规范的异常管理。因为在多人开发同一个项目的时候,如果大家都随意抛异常,那么外层的调用者会非常痛苦,因为他要写好多try catch语句来捕获异常,甚至有时候他也不知道你抛的异常是什么类型的异常。

所以在实际开发中我们一般会定义一套继承的规范异常体系,大家抛出的异常只要都是继承下来的子类对象,在外层捕获异常的时候捕获一个基类就可以了。

例如我们模拟一个服务器开发中使用的异常继承体系,有数据库异常类、缓存异常类、Http服务器异常类,它们都继承自MyExecption异常类,用随机数模拟它们概率出现异常时捕获异常的现象。

#include <vector>
#include <iostream>
#include <unistd.h>

using namespace std;

// 模拟一个服务器开发中使用的异常继承体系
class MyException
{
public:
    MyException(const string &errmsg, int id)
        : _errmsg(errmsg), _id(id)
    {
    }
    virtual string what() const
    {
        return _errmsg;
    }

protected:
    string _errmsg; // 异常错误信息
    int _id;        // 异常错误id
};

// 数据库异常类
class SqlException : public MyException
{
public:
    SqlException(const string &errmsg, int id, const string &sql)
        : MyException(errmsg, id), _sql(sql)
    {
    }
    virtual string what() const
    {
        string str = "SqlException:";
        str += _errmsg;
        str += "->";
        str += _sql;
        return str;
    }

private:
    const string _sql;
};

// 缓存异常类
class CacheException : public MyException
{
public:
    CacheException(const string &errmsg, int id)
        : MyException(errmsg, id)
    {
    }
    virtual string what() const
    {
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};

// Http服务器异常类
class HttpServerException : public MyException
{
public:
    HttpServerException(const string &errmsg, int id, const string &type)
        : MyException(errmsg, id), _type(type)
    {
    }
    virtual string what() const
    {
        string str = "HttpServerException:";
        str += _type;
        str += ":";
        str += _errmsg;

        return str;
    }

private:
    const string _type;
};

void SQLMgr()
{
    srand(time(0));
    if (rand() % 7 == 0)
    {
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    }
    // throw "xxxxxx";
}

void CacheMgr()
{
    srand(time(0));
    if (rand() % 5 == 0)
    {
        throw CacheException("权限不足", 100);
    }
    else if (rand() % 6 == 0)
    {
        throw CacheException("数据不存在", 101);
    }
    SQLMgr();
}

void HttpServer()
{
    // ...
    srand(time(0));
    if (rand() % 3 == 0)
    {
        throw HttpServerException("请求资源不存在", 100, "get");
    }
    else if (rand() % 4 == 0)
    {
        throw HttpServerException("权限不足", 101, "post");
    }
    CacheMgr();
}

int main()
{
    while (1)
    {
        sleep(1);
        try
        {
            HttpServer();
        }

        catch (const MyException &e)
        {
            // 多态
            cout << e.what() << endl;
        }
        catch (...)
        {
            cout << "Unkown Exception" << endl;
        }
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144

四、异常匹配原则

1.首先检查异常是否在try语句块内部抛出,如果是的话再查找与该try语句配套的catch语句,如果有匹配的catch语句,则直接调到该catch语句进行处理

#include <iostream>
#include <exception>

using namespace std;

void Func1()
{
	try
	{
		throw "Func1抛出异常";
	}
	catch (const char* str)
	{
		if (strcmp("Func1抛出异常", str) == 0)
		{
			cout << "Func1函数体内捕获" << endl;
		}
	}
}

int main()
{
	try
	{
		Func1();
	}
	catch (const char* str)
	{
		if (strcmp("Func1抛出异常", str) == 0)
		{
			cout << "Func1函数体内捕获" << endl;
		}
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

例如这个例子,在Func1的try语句块中抛出了异常,最后会在Func1的catch语句块中捕获这个异常。

在这里插入图片描述

2.在抛出异常时,先会到异常所在的函数体内找是否有匹配的catch语句,如果没有则返回调用的上一层函数中去找,最后一直会找到main函数体内

在这里插入图片描述

3.如果到达main函数的栈,依旧没有找到匹配的catch语句,则会终止程序。所以实际中我们在main函数的最后都要加上一个catch(…)捕获任意类型的异常,防止遗漏哪些异常没有捕获而导致程序终止

4.找到匹配的catch语句并处理完异常之后,程序会继续沿着catch子句后面继续执行

五、异常的重新抛出

有一些场景下可能单个的catch语句不能处理完成一个异常,所以在进行一些校正处理以后,可以重新抛出这个异常,给外层函数的catch语句来处理。

#include <vector>
#include <iostream>
#include <unistd.h>

using namespace std;

double Division(int a, int b)
{
    // 当b == 0时抛出异常
    if (b == 0)
    {
        throw "Division by zero condition!";
    }
    return (double)a / (double)b;
}
void Func()
{
    // 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
    // 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
    // 重新抛出去。
    int *array = new int[10];
    try
    {
        int len, time;
        cin >> len >> time;
        cout << Division(len, time) << endl;
    }
    catch (...)
    {
        cout << "delete []" << array << endl;
        delete[] array;
        throw;
    }
    // ...
    cout << "delete []" << array << endl;
    delete[] array;
}
int main()
{
    try
    {
        Func();
    }
    catch (const char *errmsg)
    {
        cout << errmsg << endl;
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

六、异常规范

异常规范的目的是为了让函数使用者知道该函数可能会抛出的异常有哪些,可以在函数的后面接throw(类型),列出这个函数可能抛出的所有异常类型。如果在函数后面接throw(),则表示函数不抛异常。

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

C++11提供了新的异常规范,如果这个函数抛出异常,则我们就不做任何声明,如果这个函数不抛出异常,则我们就在函数后面接noexcept,表示不会抛异常。

// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/714986
推荐阅读
相关标签
  

闽ICP备14008679号