[C++] C++11异常从抛出到捕获全解析

1. 概念

C语言中通过错误码的形式分类各种错误,C++11引入了在异常时抛出对象的机制,从而获取更加详细的错误信息。

2. 抛出 / 捕获异常

  • throw抛出异常,catch捕获异常
  • 抛出对象的类型和调用链决定了进入哪个catch处理
  • throw执行后直接跳到与之匹配的catch,后面的语句不执行
  • 抛出对象后会生成拷贝,在catch后销毁(类似传值返回)

使用示例:

double Divide(int a, int b)
{
    try
    {
        if (b == 0)
        {
            string s = "Divided by zero condition!" ;
            throw s;
        }
        else
        {
            return (double)a / (double)b;
        }
    }
    catch (const string& errmsg)
    {
        cout << "Divide" << ":" << errmsg << endl;
    }

    return 0;
}

int main()
{
    int x, y; cin >> x >> y;
    Divide(x, y);
    return 0;
}

发生异常,抛出了string对象s
在这里插入图片描述

3. 查找匹配

3.1 捕获规则

catch捕获异常的规则:

  1. 调用链
  2. 与该对象类型匹配
  3. 距离最近
  4. 如果到main仍未找到,则调用std::terminate函数终止程序
  5. 找到匹配的catch语句->执行catch代码块->执行catch后代码

使用示例:

double Divide(int a, int b)
{
    try
    {
        if (b == 0)
        {
            string s = "Divided by zero condition!" ;
            throw s;
        }
        else
        {
            return (double)a / (double)b;
        }
    }
    catch (const char& errmsg) //不同类型
    {
        cout << "Divide" << ":" << errmsg << endl;
    }

    return 0;
}

void Func(int a, int b)
{
    try
    {
        cout << Divide(a, b) << endl;
    }
    catch (const string& str) //同类型最近
    {
        cout << "Func" << ":" << str << endl;
    }
}

int main()
{
    int x, y; cin >> x >> y;
    Func(x, y);
    return 0;
}

输出:
在这里插入图片描述
这里可以看到跳过了Dividecatch,匹配到了调用链上层Func函数的catch

3.2 栈展开

在查找匹配的catch语句过程中会发生层层展开,步骤如下:

  1. 检查throw是否在try划定的范围内
  2. 在则查找匹配的catch
  3. 当前函数未找到,跳出到外层调用链函数查找
  4. 直到main函数未找到,调用std::terminate结束

调用链的展开:
在这里插入图片描述

3.3 查找匹配处理代码

  1. catch参数为基类指针时,可以同时捕捉基类对象和派生类对象
  2. 实际应用中,main函数内部会另设catch(...)语句,用于捕获其他异常,类型未知

另外,catch应按照引用捕获,避免拷贝开销和派生类切片问题。

下面是一个“服务器系统”的异常处理场景模拟:

class Exception
{
public:
    Exception(const string& errmsg, int id)
        : _errmsg(errmsg)
        , _id(id)
    {}

    virtual string what() const
    {
        return _errmsg;
    }

    int getid() const
    {
        return _id;
    }

protected:
    string _errmsg;
    int _id;
};

class SqlException : public Exception
{
public:
    SqlException(const string& errmsg, int id, const string& sql)
        : Exception(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 Exception
{
public:
    CacheException(const string& errmsg, int id)
        : Exception(errmsg, id)
    {}

    virtual string what() const
    {
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};

class HttpException : public Exception
{
public:
    HttpException(const string& errmsg, int id, const string& type)
        : Exception(errmsg, id)
        , _type(type)
    {}

    virtual string what() const
    {
        string str = "HttpException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }

private:
    const string _type;
};

void SQLMgr()
{
    if (rand() % 7 == 0)
    {
        throw SqlException("权限不⾜", 100, "select * from name = '张三'");
    }
    else
    {
        cout << "SQLMgr 调⽤成功" << endl;
    }
}

void CacheMgr()
{
    if (rand() % 5 == 0)
    {
        throw CacheException("权限不⾜", 100);
    }
    else if (rand() % 6 == 0)
    {
        throw CacheException("数据不存在", 101);
    }
    else
    {
        cout << "CacheMgr 调⽤成功" << endl;
    }

    SQLMgr();
}

void HttpServer()
{
    if (rand() % 3 == 0)
    {
        throw HttpException("请求资源不存在", 100, "get");
    }
    else if (rand() % 4 == 0)
    {
        throw HttpException("权限不⾜", 101, "post");
    }
    else
    {
        cout << "HttpServer调⽤成功" << endl;
    }

    CacheMgr();
}

int main()
{
    srand(time(0));

    while (1)
    {
        this_thread::sleep_for(chrono::seconds(1)); //每隔1秒执行一次循环体

        try
        {
            HttpServer(); //网页端
        }
        catch (const Exception& e) // 参数接收基类,基类对象和派生类对象都可以被捕获
        {
            cout << e.what() << endl;
        }
        catch (...)
        {
            cout << "Unkown Exception" << endl;
        }
    }

    return 0;
}



//异常重新抛出
//因为网络异常发不出去则就需要捕获异常再重新抛出;错误并非源自网路差,也要重新抛出。
void _SeedMsg(const string& s)
{
    if (rand() % 2 == 0)
    {
        throw HttpException("⽹络不稳定,发送失败", 102, "put");
    }
    else if (rand() % 7 == 0)
    {
        throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
    }
    else
    {
        cout << "发送成功" << endl;
    }
}

void SendMsg(const string& s)
{
    // 发送消息失败,则再重试3次
    for (size_t i = 0; i < 4; i++)
    {
        try
        {
            _SeedMsg(s);
            break;
        }
        catch (const Exception& e)
        {
            if (e.getid() == 102)
            {
                // 重试三次以后否失败了,则说明网络太差了,重新抛出异常
                if (i == 3)
                    throw;

                cout << "开始第" << i + 1 << "重试" << endl;
            }
            else //并非网络异常引起的错误,交由外层函数处理(main)
            {
                throw;
            }
        }
    }
}

int main()
{
    srand(time(0));

    string str;
    while (cin >> str)
    {
        try
        {
            SendMsg(str);
        }
        catch (const Exception& e)
        {
            cout << e.what() << endl << endl;
        }
        catch (...)
        {
            cout << "Unkown Exception" << endl;
        }
    }
    return 0;
}

部分输出如下:

在这里插入图片描述

4. 安全问题

《c++effective》条款:别让异常逃离析构函数。

若开辟了资源,在资源释放前抛出异常,则会发生内存泄漏的安全问题。解决方法是使用智能指针,这点后续文章会详细讲解。

5. 规范

  • C++98:throw()表示不抛异常,throw(参数...)表示抛出指定类型的异常
  • C++11:noexcept作为关键字放在函数后表示不抛异常;作为运算符使用,判断函数是否可能会抛异常(true不抛,反之false

使用示例:

//作为关键字
double Divide(int a, int b)
{
    try
    {
        if (b == 0)
        {
            string s = "Divided by zero condition!" ;
            throw s;
        }
        else
        {
            return (double)a / (double)b;
        }
    }
    catch (const string& errmsg)
    {
        cout << "Divide" << ":" << errmsg << endl;
    }

    return 0;
}

int Add(int a, int b) noexcept
{
    return a + b;
}

int main()
{
    //作为运算符
    int i;
    cout << noexcept(Add(1, 2)) << endl;
    cout << noexcept(Divide(1, 0)) << endl;
    cout << noexcept(++i) << endl;
    return 0;
}

输出:
在这里插入图片描述
感谢阅读,我们下篇见。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值