【C++】C++异常机制

这篇具有很好参考价值的文章主要介绍了【C++】C++异常机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++异常

C语言传统的错误处理方式

  • 终止程序,如assert直接断言报错,缺陷:非常麻烦,如果发生内存错误,除零错误会立即终止程序

  • 返回错误码。缺陷:需要程序员自己去查渣哦对应的错误,如系统库的接口函数都是通过错误码放到errno中,需要程序员自己去读区错误码进行错误处理

  • C标准库中的setjmp和longjmp组合(不常用)

实际上C语言基本都是使用返回错误码来处理错误,部分情况下使用终止程序处理非常严重的错误

C++错误处理方式

C++可以使用异常来对错误进行处理,异常是面向对象语言常用的错误处理方式,当一个函数发现自己出现无法处理的错误的时候就可以抛出异常,让该函数的直接或间接调用者处理这个错误

  • throw : 当程序出现错误,可以通过throw关键字抛出一个异常
  • try : try块中防治当是可能抛出异常的代码,该代码块在执行时会进行异常错误检测,try块后面通常会多跟一个catch块
  • catch : 如果try中发生错误,那么就会跳到对应的catch块执行对应的代码
try {
				// 可能出错的代码
}
catch (ExceptionName e1) {
				// catch 块1
}
catch (ExceptionName e2) {
        // catch 块2
}
// 每一个catch块对应一种错误

不同的catch块对应一种不同的错误

异常的使用方法

异常抛出和捕获的匹配原则

1、异常是通过抛出对象引发的,该对象类型决定应该被哪一个catch模块捕获(有点像函数重载🤔️),如果抛出的异常对象没有被捕获,或是没有匹配类型的捕获,那么这个程序会终止报错

2、被选中的处理代码(catch块)时调用链中与该对象类型匹配且距离抛出异常位置最近的那个

3、抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝对象的临时对象会在catch后被销毁(类似函数的传值返回)

4、catch(…)可以捕获任意类型的异常,但是捕获后无法知道异常错误是什么

5、实际上异常的抛出和捕获的匹配原则有一个例外,捕获和抛出的异常类型并不一定要完全匹配,可以抛出派生类对象,使用基类对象进行捕获,这个在实际生产中使用的很多

函数调用链中异常展开的匹配规则

1、当异常被抛出后,首先会检查throw是否在try块内部,如果在则查找匹配的catch语句,如果有匹配的,就跳到catch地方进行处理

2、如果当前函数栈没有匹配的catch则会退出当前的函数栈,返回上一个函数调用栈进行匹配catch。找到匹配的字句进行处理后,会沿着catch字句后面继续执行,不会再回到原来抛出异常的地方

3、如果到达main函数的栈仍然没有找到匹配的catch,则会终止程序

void func3() {
    throw string("异常来了");
}
void func2() {
    try{
        func3();
    } catch (const string& s) {						// 捕获string类型的异常
        cout << "func2 catch :" << s << endl;
        throw int(1);
    }
}
void func1() { func2(); }

int main() {
    try{
        func1();
    } catch (const string& s) {   				// 用于捕获string类型的异常
        cout << "出错啦 : " << s << endl;
    } catch (...) {												// 用于捕获非string类型的异常
        cout << "出现未知错误" << endl;
    }
    cout << "main 函数结束了" << endl;
    return 0;
}

可以看到func1,func2,func3依次被调用,在func3中抛出了一个异常,但是func3中并没有try块,也没有捕获程序,就会退回上一个函数调用栈(func)中查找,可以看到func3()是在try块中的,第一个步骤,检查throw是否在try内部成立,接下来查找匹配的throw字句

然后查看func2内部的catch块类型(因为其是最近的),发现类型是匹配的,就捕获了这个异常,然后又抛出了一个int类型的异常,最终在main函数处被捕获。被捕获后继续执行后续代码

这个沿用调用链查找匹配catch字句的过程称为栈展开,实际过程中最后都要加上一个catch(…)捕获任意类型的异常,否则异常没有被捕获,程序就会被终止

异常的使用规范

有时候单个catch不能完全处理一个异常,在进行一些矫正处理后,希望将异常再交给更外层的调用链函数进行处理,比如最外层可能需要拿到异常进行日志信息的记录,这就要重新抛出异常递交给更上层的函数处理

void func2() {
    throw string("这是个异常");
}

void func1() {
    int* arr = new int[10];
    func2();
    delete[] arr;
}

int main() {
    try {
       func1();
    } catch(const string& s) {
        cout << "捕获字符串异常:" << s << endl;
    } catch(...) {
        cout << "捕获未知异常" << endl;
    }
  return 0}

可以看到这段代码有一点小问题,在函数func1中,使用new在堆上开辟了一块四十字节的空间,之后调用func2函数,func2函数抛出异常被main函数的catch块捕获,然后执行后续代码,main函数结束

可以看到,从始至终,我们在堆上开辟的arr空间并没有被delete,造成了内存泄漏

void func1() {
    int* arr = new int[10];
    try{
        func2();
    } catch (const string& s) {
        delete[] arr;
        cout << "func 2 get a 异常" << endl;
        string func2_exception = "func2" + s;
        throw string(func2_exception);
    }
}

对代码进行简单修改,可以看到我们将delete[] arr的操作放在了func1函数的catch块中,成功对func1函数遗留下的问题进行处理,然后将异常再次抛出

如果这个异常需要加入这个函数的信息,我们可以重新构建异常信息,如果不需要我们可以直接一下结构进行抛出


try {
		func2();
} catch (...) {   // 捕获任意类型异常  
		delete[] arr;
		throw;				// 直接抛出让外层处理
}

异常安全问题

由抛异常导致的安全问题叫做异常安全问题,对于异常安全问题下面有几点建议

1、构造函数完成对象构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整,没有完整出实话,析构函数同理

2、C++中异常经常会出现资源泄漏的问题,如在new和delete之间的代码抛出异常,导致内存泄漏,在lock和unlock之间抛出异常会导致死锁,C++中使用RALL的方式来解决该问题

异常规范

为了能让函数使用者知道某个函数会抛出哪一些异常,C++标准规定:

1、在函数后面接throw(type1, type2, …) 列出这个函数可能抛出的所有异常类型,便于检查理解

2、在函数后面接throw() 或 noexcept(C++11),标识该函数不会抛出异常

3、异常接口声明不是强制的

void func()1 throw(A, B, C, D);			// 可能抛出A,B,C,D类型的异常
void func()2 throw(std::bad_alloc); //只会抛出bad_alloc类型的异常
void func()3 throw();								// 不会抛出异常

自定义异常体系

实际上很多公司都会自定义自己的异常体系进行规范异常处理

  • 公司的项目一般会进行模块划分,不同程序猿小组完成不同模块,如果不对异常进行规范,那么负责外层捕获异常的程序猿就很难受了,内部函数抛出的异常类型千奇百怪,都要一一捕获。如果不进行统一很容易出现错误
  • 实际开发场景中,都会定义一套集成的规范异常体系,先定义一个最基础的基异常类,所有人抛出的异常都必须是继承于该异常类的派生类,异常语法规定可以用基类捕获派生类对象,因此最外层只需要捕获基类就可以了

最基础的异常类至少包括错误编号和错误描述两个成员变量,甚至还可以包含当前函数栈帧的调用链等信息。该异常一般还会提供两个成员函数用于获取错误编号和错误描述

class MyException{
public:
    MyException(int _err_id, const string& _err_msg)
        : err_id(_err_id), err_msg(_err_msg)
        {}
        int GetErrid() const { return err_id; }
        virtual string what() const { return err_msg; }
private:
    int err_id;       // 错误编号
    string err_msg;   // 错误描述
};

​ 如果其他模块想要对异常类进行扩展,必须要继承这个基础的异常类,可以在派生类中按需添加成员变量,或者对继承的what函数进行重写,使其能告诉程序猿更多异常信息


class SqlException : public MyException {
public:
    SqlException(int _err_id, const char* _err_msg, const char* _err_sql)
        : MyException(_err_id, _err_msg)
        , err_sql(_err_sql) {}
    virtual string what(){
        string msg = "CacheException: ";
        msg += err_msg;
        msg += "sql 语句:";
        msg += err_sql;
        return msg;
    }
protected:
    std::string err_sql;
};

注意一下:继承体系中成员变量一般都不用私有,不然在子类中不可见。基类Exception中的what函数可以定义成虚函数,方便自类重写,赋予其更强大的效果

STL中的异常体系

C++标准库中的异常也是一个基础的异常体系,其中exception就是异常基类,我们可以在程序中使用这些标准异常

int main() {
    try {
        vector<int> v(10, 5);
        //  这里如果系统内存不够了就会抛异常
         v.reserve(100000000000000);
        // 这里越界也会抛异常
        v.at(10) = 100;
    } catch (const exception& e) {
        cout << e.what() << endl;    // std::bad_alloc
    } catch (...) {
        cout << "Unknow Exception" << endl;
    }
    return 0;
}

【C++】C++异常机制,C++学习,c++,jvm

  • exceptino类的what成员函数和西沟函数都定义成了虚函数,方便字类对其进行重写,从而达到多态的效果
  • 日常开发我们也可以继承exception来实现我们自己的异常类,实际上公司都有自己的一套异常体系

异常的优缺点

C++异常的优点:

1、异常对象定义好了,相比于错误码的方式可以更加清晰准确的展示错误的各种信息,甚至可以包含堆栈调用信息,这样可以帮助更好的定位程序bug

2、返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们层层返回错误在最外层才可以拿到错误

3、很多第三方库也都包含异常,如boost、gtest、gmock等等常用的哭,那么我们使用它们也要用到异常

4、很多测试框架都使用异常,这样可以更好的使用单元测试等进行白盒测试

5、部分函数的使用异常更好处理,比如构造函数没有返回值,不方便使用错误码处理,比如T& operator这样的函数,如果pos越界了就只能使用异常或者终止程序,没有办法通过返回值表示错误

C++异常的缺点

1、异常会导致程序执行流乱跳,并且非常混乱,运行时出错抛异常就会乱跳。导致我们跟踪分析程序时会很困难

2、C++没有垃圾回收机制,资源需要自己管理,有了异常非常容易内存泄漏,出现死锁等异常安全问题

3、C++标准库的异常体系定义的不好,导致大家各自定义各自的异常体系,非常的混乱

4、异常尽量规范使用,否则后果不堪设想,随意抛异常,外层用户苦不堪言。所以异常规范有两点 一、抛出的异常都必须继承于一个基类 二、函数是否抛异常,抛什么异常需要使用throw(), noexcept的方式进行规范文章来源地址https://www.toymoban.com/news/detail-568574.html

到了这里,关于【C++】C++异常机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入学习JVM —— GC垃圾回收机制

            前面荔枝已经梳理了有关JVM的体系结构和类加载机制,也详细地介绍了JVM在类加载时的双亲委派模型,而在这篇文章中荔枝将会比较详细地梳理有关JVM学习的另一大重点——GC垃圾回收机制的相关知识,重点了解的比如对象可达性的判断、四种回收算法、分代回收

    2024年02月14日
    浏览(45)
  • 深度解析C++异常处理机制:分类、处理方式、常见错误及11新增功能

    异常是程序在运行过程中出现非正常情况的处理机制。当出现异常时程序会停止运行并调用异常处理程序。 异常可以分为内置异常和自定义异常 2.1 内置异常 C++ 标准库提供了许多预定义的异常类,称为内置异常,包括以下几种: std::exception :所有标准异常类的基类。 std::

    2024年01月18日
    浏览(42)
  • JVM学习 GC垃圾回收机制 (堆内存结构、GC分类、四大垃圾回收算法)

    🤖 作者简介: 努力的clz ,一个努力编程的菜鸟 🐣🐤🐥   👀 文章专栏: 《JVM 学习笔记》 ,本专栏会专门记录博主在学习 JVM 中学习的知识点,以及遇到的问题。   🙉 文章详情: 本篇博客是学习 【狂神说Java】JVM快速入门篇 的学习笔记,关于 GC垃圾回收机制 (堆内存结

    2023年04月19日
    浏览(41)
  • 【C++】C++关于异常的学习

    文章目录 C语言传统的处理错误的方式 一、异常的概念及用法 二、自定义异常体系 总结 传统的错误处理机制: 1. 终止程序,如 assert ,缺陷:用户难以接受。如发生内存错误,除 0 错误时就会终止程序。 2. 返回错误码 ,缺陷:需要程序员自己去查找对应的错误。如系统的

    2024年02月10日
    浏览(30)
  • 【C++学习】异常

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! C语言传统处理错误的方式: 终止程序 比如空指针解引用,除0等异常发生时,程序会直接终止,但是这种方式对于用户来说难以接受,会导致整个进程挂掉。 返回错误码 比如打开文件

    2024年02月06日
    浏览(27)
  • C++学习笔记——友元、嵌套类、异常

    目录 一、友元 一个使用友元的示例代码 输出结果 二、嵌套类 一个使用嵌套类的示例代码 输出结果 三、异常 一个使用异常处理的示例代码 输出结果 四、结论 五、使用它们的注意事项 上一篇文章链接: C++中的继承和模板是非常强大和灵活的特性,它们可以帮助我们实现代

    2024年02月02日
    浏览(108)
  • C++ 学习系列 二 -- RAII 机制

     RAII ( R esource  A cquisition  I s  I nitialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为 资源获取即初始化, 其含义是:用局部对象来管理资源的技术,这里所说的资源指的是操作系统中的内存资源、网络套接字等等;局部对象指的是定义在栈上的对象,其生命周期的管

    2024年02月13日
    浏览(48)
  • 【JVM】JVM类加载机制

    JVM的类加载机制,就是把类,从硬盘加载到内存中 Java程序,最开始是一个Java文件,编译成.class文件,运行Java程序,JVM就会读取.class文件,把文件的内容,放到内存中,并且构造成.class类对象 这里的加载是整个类加载的一个阶段,他和类加载是不同的 在整个类加载的过程中 主要任务就是

    2024年02月07日
    浏览(49)
  • JVM类加载机制-JVM(一)

    我们运行一个.class文件,windows下的java.exe调用底层jvm.dll文件创建java虚拟机(c++实现)。 创建一个引导类加载器实例(c++实现) C++调用java代码Launcher,该类创建其他java类加载器。 Launcher.getClassLoader()调用loaderClass加载运行类Math classLoader.loader(“com.jvm.math”)加载main方法入口

    2024年02月12日
    浏览(44)
  • JVM基础(1)——JVM类加载机制

    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖,挖的越深,基础越扎实! 阶段1、深入多线程 阶段2、深入多线程设计模式 阶段3、深入juc源码解析

    2024年02月02日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包