C++ 异常

std::exception标准和各平台实现的不同 C++98标准: C++11标准: g++ 5.4.0实现如下: VS2015实现如下: 可见,g++只是实现了标准中的默认构造函数;而VS扩展了标准,新增了explicit exception(char const* const _Message) throw()和exception(char const* const _Message, int) throw();即像t... 阅读详情

一、什么是异常处理

        一句话:异常处理就是处理程序中的错误。

二、为什么需要异常处理,以及异常处理的基本思想

        C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现)。

        Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。 
The fundamental idea is that a function that finds a problem it cannot cope with throws an exception, hoping that its (direct or indirect) caller can handle the problem.

        也就是《C++ primer》中说的:将问题检测和问题处理相分离。 
Exceptions let us separate problem detection from problem resolution

        一种思想:在所有支持异常处理的编程语言中(例如java),要认识到的一个思想:在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容,实际上完成了两个部分的通信,通信的内容是“出现了什么错误”。当然,各种语言对异常的具体实现有着或多或少的区别,但是这个通信的思想是不变的。

三、异常出现之前处理错误的方式

        在C语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。

        这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。

        还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。

四、异常为什么好

    在如果使用异常处理的优点有以下几点:

        1. 函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。

        2. 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。

        3. 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。

        4. 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。

五、C++中使用异常时应注意的问题

    任何事情都是两面性的,异常有好处就有坏处。如果你是C++程序员,并且希望在你的代码中使用异常,那么下面的问题是你要注意的。

        1. 性能问题。这个一般不会成为瓶颈,但是如果你编写的是高性能或者实时性要求比较强的软件,就需要考虑了。

(如果你像我一样,曾经是java程序员,那么下面的事情可能会让你一时迷糊,但是没办法,谁叫你现在学的是C++呢。)

       2. 指针和动态分配导致的内存回收问题:在C++中,不会自动回收动态分配的内存,如果遇到异常就需要考虑是否正确的回收了内存。在java中,就基本不需要考虑这个,有垃圾回收机制真好!

        3. 函数的异常抛出列表:java中是如果一个函数没有在异常抛出列表中显式指定要抛出的异常,就不允许抛出;可是在C++中是如果你没有在函数的异常抛出列表指定要抛出的异常,意味着你可以抛出任何异常。

        4. C++中编译时不会检查函数的异常抛出列表。这意味着你在编写C++程序时,如果在函数中抛出了没有在异常抛出列表中声明的异常,编译时是不会报错的。而在java中,eclipse的提示功能真的好强大啊!

        5. 在java中,抛出的异常都要是一个异常类;但是在C++中,你可以抛出任何类型,你甚至可以抛出一个整型。(当然,在C++中如果你catch中接收时使用的是对象,而不是引用的话,那么你抛出的对象必须要是能够复制的。这是语言的要求,不是异常处理的要求)。

        6. 在C++中是没有finally关键字的。而java和python中都是有finally关键字的。

六、异常的基本语法

1. 抛出和捕获异常

        很简单,抛出异常用throw,捕获用try……catch。

        捕获异常时的注意事项:

             1. catch子句中的异常说明符必须是完全类型,不可以为前置声明,因为你的异常处理中常常要访问异常类的成员。例外:只有你的catch子句使用指针或者引用接收参数,并且在catch子句内你不访问异常类的成员,那么你的catch子句的异常说明符才可以是前置声明的类型。

             2. catch的匹配过程是找最先匹配的,不是最佳匹配。

             3. catch的匹配过程中,对类型的要求比较严格。不允许标准算术转换和类类型的转换。(类类型的转化包括两种:通过构造函数的隐式类型转化和通过转化操作符的类型转化)。

             4. 和函数参数相同的地方有: 
                    ① 如果catch中使用基类对象接收子类对象,那么会造成子类对象分隔(slice)为父类子对象(通过调用父类的复制构造函数); 
                    ② 如果catch中使用基类对象的引用接受子类对象,那么对虚成员的访问时,会发生动态绑定,即会多态调用。 
                    ③ 如果catch中使用基类对象的指针,那么一定要保证throw语句也要抛出指针类型,并且该指针所指向的对象,在catch语句执行是还存在(通常是动态分配的对象指针)。

             5. 和函数参数不同的地方有:   
                    ① 如果throw中抛出一个对象,那么无论是catch中使用什么接收(基类对象、引用、指针或者子类对象、引用、指针),在传递到catch之前,编译器都会另外构造一个对象的副本。也就是说,如果你以一个throw语句中抛出一个对象类型,在catch处通过也是通过一个对象接收,那么该对象经历了两次复制,即调用了两次复制构造函数。一次是在throw时,将“抛出到对象”复制到一个“临时对象”(这一步是必须的),然后是因为catch处使用对象接收,那么需要再从“临时对象”复制到“catch的形参变量”中; 如果你在catch中使用“引用”来接收参数,那么不需要第二次复制,即形参的引用指向临时变量。 
                    ② 该对象的类型与throw语句中体现的静态类型相同。也就是说,如果你在throw语句中抛出一个指向子类对象的父类引用,那么会发生分割现象,即只有子类对象中的父类部分会被抛出,抛出对象的类型也是父类类型。(从实现上讲,是因为复制到“临时对象”的时候,使用的是throw语句中类型的(这里是父类的)复制构造函数)。 
                    ③ 不可以进行标准算术转换和类的自定义转换:在函数参数匹配的过程中,可以进行很多的类型转换。但是在异常匹配的过程中,转换的规则要严厉。

                    ④ 异常处理机制的匹配过程是寻找最先匹配(first fit),函数调用的过程是寻找最佳匹配(best fit)。

2. 异常类型

        上面已经提到过,在C++中,你可以抛出任何类型的异常。(哎,竟然可以抛出任何类型,刚看到到这个的时候,我半天没反应过来,因为java中这样是不行的啊)。

         注意:也是上面提到过的,在C++中如果你throw语句中抛出一个对象,那么你抛出的对象必须要是能够复制的。因为要进行复制副本传递,这是语言的要求,不是异常处理的要求。(在上面“和函数参数不同的地方”中也讲到了,因为是要复制先到一个临时变量中)

3. 栈展开

        栈展开指的是:当异常抛出后,匹配catch的过程。

        抛出异常时,将暂停当前函数的执行,开始查找匹配的catch子句。沿着函数的嵌套调用链向上查找,直到找到一个匹配的catch子句,或者找不到匹配的catch子句。

        注意事项:

               1. 在栈展开期间,会销毁局部对象。

                     ① 如果局部对象是类对象,那么通过调用它的析构函数销毁。

                     ② 但是对于通过动态分配得到的对象,编译器不会自动删除,所以我们必须手动显式删除。(这个问题是如此的常见和重要,以至于会用到一种叫做RAII的方法,详情见下面讲述)

               2. 析构函数应该从不抛出异常。如果析构函数中需要执行可能会抛出异常的代码,那么就应该在析构函数内部将这个异常进行处理,而不是将异常抛出去。

                     原因:在为某个异常进行栈展开时,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库 terminate 函数。而默认的terminate 函数将调用 abort 函数,强制从整个程序非正常退出。

               3. 构造函数中可以抛出异常。但是要注意到:如果构造函数因为异常而退出,那么该类的析构函数就得不到执行。所以要手动销毁在异常抛出前已经构造的部分。

4. 异常重新抛出

        语法:使用一个空的throw语句。即写成: throw;   

        注意问题:

                ① throw;  语句出现的位置,只能是catch子句中或者是catch子句调用的函数中。 
                ② 重新抛出的是原来的异常对象,即上面提到的“临时变量”,不是catch形参。 
                ③ 如果希望在重新抛出之前修改异常对象,那么应该在catch中使用引用参数。如果使用对象接收的话,那么修改异常对象以后,不能通过“重新抛出”来传播修改的异常对象,因为重新抛出不是catch形参,应该使用的是 throw e;  这里“e”为catch语句中接收的对象参数。

5. 捕获所有异常(匹配任何异常)

        语法:在catch语句中,使用三个点(…)。即写成:catch (…)   这里三个点是“通配符”,类似 可变长形式参数。

        常见用法:与“重新抛出”表达式一起使用,在catch中完成部分工作,然后重新抛出异常。

6. 未捕获的异常

        意思是说,如果程序中有抛出异常的地方,那么就一定要对其进行捕获处理。否则,如果程序执行过程中抛出了一个异常,而又没有找到相应的catch语句,那么会和“栈展开过程中析构函数抛出异常”一样,会 调用terminate 函数,而默认的terminate 函数将调用 abort 函数,强制从整个程序非正常退出。

7. 构造函数的函数测试块

        对于在构造函数的初始化列表中抛出的异常,必须使用函数测试块(function try block)来进行捕捉。语法类型下面的形式:

  1. MyClass::MyClass(int i)  
  2. try :member(i) {  
  3.     //函数体  
  4. } catch(异常参数) {  
  5.     //异常处理代码  
  6. }  

        注意事项:在函数测试块中捕获的异常,在catch语句中可以执行一个内存释放操作,然后异常仍然会再次抛出到用户代码中。

8. 异常抛出列表(异常说明 exception specification)

        就是在函数的形参表之后(如果是const成员函数,那么在const之后),使用关键字throw声明一个带着括号的、可能为空的 异常类型列表。形如:throw ()  或者 throw (runtime_error, bad_alloc)   。

        含义:表示该函数只能抛出 在列表中的异常类型。例如:throw() 表示不抛出任何异常。而throw (runtime_error, bad_alloc)表示只能抛出runtime_error 或bad_alloc两种异常。

        注意事项:(以前学java的尤其要注意,和java中不太一样)

                ① 如果函数没有显式的声明 抛出列表,表示异常可以抛出任意列表。(在java中,如果没有异常抛出列表,那么是不能抛出任何异常的)。

                ② C++的 “throw()”相当于java的不声明抛出列表。都表示不抛出任何异常。

                ③ 在C++中,编译的时候,编译器不会对异常抛出列表进行检查。也就是说,如果你声明了抛出列表,即使你的函数代码中抛出了没有在抛出列表中指定的异常,你的程序依然可以通过编译,到运行时才会出错,对于这样的异常,在C++中称为“意外异常”(unexpeced exception)。(这点和java又不相同,在java中,是要进行严格的检查的)。 

        意外异常的处理: 
                如果程序中出现了意外异常,那么程序就会调用函数unexpected()。这个函数的默认实现是调用terminate函数,即默认最终会终止程序。

        虚函数重载方法时异常抛出列表的限制: 
                在子类中重载时,函数的异常说明 必须要比父类中要同样严格,或者更严格。换句话说,在子类中相应函数的异常说明不能增加新的异常。或者再换句话说:父类中异常抛出列表是该虚函数的子类重载版本可以抛出异常列表的 超集。

        函数指针中异常抛出列表的限制: 
                 异常抛出列表是函数类型的一部分,在函数指针中也可以指定异常抛出列表。但是在函数指针初始化或者赋值时,除了要检查返回值和形式参数外,还要注意异常抛出列表的限制:源指针的异常说明必须至少和目标指针的一样严格。比较拗口,换句话说,就是声明函数指针时指定的异常抛出列表,一定要实际函数的异常抛出列表的超集。 如果定义函数指针时不提供异常抛出列表,那么可以指向能够抛出任意类型异常的函数。                

        抛出列表是否有用:   
                 在《More effective C++》第14条,Scott Meyers指出“要谨慎的使用异常说明”(Use exception specifications judiciously)。“异常说明”,就是我们所有的“异常抛出列表”。之所以要谨慎,根本原因是因为C++编译器不会检查异常抛出列表,这样就可能在函数代码中、或者调用的函数中抛出了没有在抛出列表中指定的异常,从而导致程序调用unexpected函数,造成程序提前终止。同时他给出了三条要考虑的事情: 
                         ① 在模板中不要使用异常抛出列表。(原因很简单,连用来实例模板的类型都不知道,也就无法确定该函数是否应该抛出异常,抛出什么异常)。  
                         ② 如果A函数内调用了B函数,而B函数没有声明异常抛出列表,那么A函数本身也不应该设定异常抛出列表。(原因是,B函数可能抛出没有在A函数的异常抛出列表中声明的异常,会导致调用unex函数); 
                         ③ 通过set_unexpected函数指定一个新的unexpected函数,在该函数中捕获异常,并抛出一个统一类型的异常。

                 另外,在《C++ Primer》4th 中指出,虽然异常说明应用有限,但是如果能够确定该函数不会抛出异常,那么显式声明其不抛出任何异常 有好处。通过语句:"throw ()"。这样的好处是:对于程序员,当调用这样的函数时,不需要担心异常。对于编译器,可以执行被可能抛出异常所抑制的优化。

七、标准库中的异常类

        和java一样,标准库中也提供了很多的异常类,它们是通过类继承组织起来的。标准异常被组织成八个。

        异常类继承层级结构图如下: 
C++  标准库异常类继承层次图

    每个类所在的头文件在图下方标识出来.

    标准异常类的成员: 
        ① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。 
        ② logic_error类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述; 
        ③ 所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息。

    标准异常类的具体描述: 

异常名称

描述

exception 所有标准异常类的父类
bad_alloc 当operator new and operator new[],请求分配内存失败时
bad_exception 这是个特殊的异常,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常抛出列表中没有的异常,这是调用的unexpected函数中若抛出异常,不论什么类型,都会被替换为bad_exception类型
bad_typeid 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常
bad_cast 使用dynamic_cast转换引用失败的时候
ios_base::failure io操作过程出现错误
logic_error 逻辑错误,可以在运行前检测的错误
runtime_error 运行时错误,仅在运行时才可以检测的错误

        logic_error的子类: 

 

异常名称

描述

length_error 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作
domain_error 参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数
out_of_range 超出有效范围
invalid_argument 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常

 

        runtime_error的子类: 

 

异常名称

描述

range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢
underflow_error 算术计算下溢

 

八、编写自己的异常类

        1. 为什么要编写自己的异常类? 
                ① 标准库中的异常是有限的; 
                ② 在自己的异常类中,可以添加自己的信息。(标准库中的异常类值允许设置一个用来描述异常的字符串)。

        2. 如何编写自己的异常类? 
                ① 建议自己的异常类要继承标准异常类。因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。 
                ② 当继承标准异常类时,应该重载父类的what函数和虚析构函数。 
                ③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数。

九、用类来封装资源分配和释放

        为什么要使用类来封装资源分配和释放?  
                为了防止内存泄露。因为在函数中发生异常,那么对于动态分配的资源,就不会自动释放,必须要手动显式释放,否则就会内存泄露。而对于类对象,会自动调用其析构函数。如果我们在析构函数中显式delete这些资源,就能保证这些动态分配的资源会被释放。

        如何编写这样的类?  
                将资源的分配和销毁用类封转起来。在析构函数中要显式的释放(delete或delete[])这些资源。这样,若用户代码中发生异常,当作用域结束时,会调用给该类的析构函数释放资源。这种技术被称为:资源分配即初始化。(resource allocation is initialization,缩写为"RAII")。

十、auto_ptr的使用(非常重要)

        “用类封装资源的分配和释放”是如此的重要,C++标准库为我们提供了一个模板类来实现这个功能。名称为auto_ptr,在memory头文件中。

        auto_ptr类的成员如下:(摘自《C++ Primer》) 

函数

功能

auto_ptr <T> ap() 默认构造函数,创建名为ap的未绑定的auto_ptr对象
auto_ptr<T> ap(p); 创建名为 ap 的 auto_ptr 对象,ap 拥有指针 p 指向的对象。该构造函数为 explicit
auto_ptr<T> ap1(ap2); 创建名为 ap1 的 auto_ptr 对象,ap1 保存原来存储在 ap2 中的指针。将所有权转给 ap1,ap2 成为未绑定的 auto_ptr 对象
ap1 = ap2 将所有权 ap2 转给 ap1。删除 ap1 指向的对象并且使 ap1 指向 ap2 指向的对象,使 ap2 成为未绑定的
~ap 析构函数。删除 ap 指向的对象
*ap 返回对 ap 所绑定的对象的引用
ap-> 返回 ap 保存的指针
ap.reset(p) 如果 p 与 ap 的值不同,则删除 ap 指向的对象并且将 ap 绑定到 p
ap.release() 返回 ap 所保存的指针并且使 ap 成为未绑定的
ap.get() 返回 ap 保存的指针

        auto_ptr类的使用: 
                1. 用来保存一个指向对象类型的指针。注意必须是动态分配的对象(即使用new非配的)的指针。既不能是动态分配的数组(使用new [])指针,也不能是非动态分配的对象指针。 
                2. 惯用的初始化方法:在用户代码中,使用new表达式作为auto_ptr构造函数的参数。(注意:auto_ptr类接受指针参数的构造函数为explicit,所以必须显式的进行初始化)。 
                3. auto_ptr的行为特征:类似普通指针行为。auto_ptr存在的主要原因就是,为了防止动态分配的对象指针造成的内存泄露,既然是指针,其具有"*"操作符和"->"操作符。所以auto_ptr的主要目的就是:首先保证自动删除auto_ptr所引用的对象,并且要支持普通指针行为。 
                4. auto_ptr对象的复制和赋值是有破坏性的。① 会导致右操作数成为未绑定的,导致auto_ptr对象不能放到容器中;② 在赋值的时候,将有操作符修改为未绑定,即修改了右操作数,所以要保证这里的赋值操作符右操作数是可以修改的左值(然而普通的赋值操作符中,右操作数可以不是左值);③和普通的赋值操作符一样,如果是自我赋值,那么没有效果;④ 导致auto_ptr对象不能放到容器中。 
                5. 如果auto_ptr初始化的时候,使用默认构造函数,成为未绑定的auto_ptr对象,那么可以通过reset操作将其绑定到一个对象。 
                6. 如果希望测试auto_ptr是否已经绑定到了一个对象,那么使用get()函数的返回值与NULL进行比较。

       auto_ptr的缺陷: 
                1. 不能使用auto_ptr对象保存指向静态分配的对象的指针,也不能保存指向动态分配的数组的指针。 
                2. 不能讲两个auto_ptr对象指向同一个对象。因为在一个auto_ptr对象析构以后,造成另一个auto_ptr对象指向了已经释放的内存。造成这种情况的两种主要常见原因是:① 用同一个指针来初始化或者reset两个不同的auto_ptr对象;② 使用一个auto_ptr对象的get函数返回值去初始化或者reset另一个auto_ptr对象。 
                3. 不能将auto_ptr对象放到容器中。因为其复制和赋值操作具有破坏性。

十一、常见的异常处理问题

    动态内存分配错误

         ① 分配动态内存使用的是new和new[]操作符,如果他们分配内存失败,就会抛出bad_alloc异常,在new头文件中,所以我们的代码中应该捕捉这些异常。常见的代码形式如下:

  1. try {  
  2.     //其他代码  
  3.     ptr = new int[num_max];  
  4.     //其他代码  
  5. } catch(bad_alloc &e) {  
  6.     //这里常见的处理方式为:先释放已经分配的内存,然后结束程序,或者打印一条错误信息并继续执行  
  7. }  

         ② 可以使用类似C语言的方式处理,但这时要使用的nothrow版本,使用"new (nothrow)"的形式分配内存。这时,如果分配不成功,返回的是NULL指针,而不再是抛出bad_alloc异常。 
         ③ 可以定制内存分配失败行为。C++允许指定一个new 处理程序(newhandler)回调函数。默认的并没有new 处理程序,如果我们设置了new 处理程序,那么当new和new[] 分配内存失败时,会调用我们设定的new 处理程序,而不是直接抛出异常。通过set_new_handler函数来设置该回调函数。要求被回调的函数没有返回值,也没有形式参数。

十二、来自C++之父Bjarne Stroustrup的建议

    节选自《The C++ Programming Language》 ——C++之父Bjarne Stroustrup 
         1. Don’t use exceptions where more local control structures will suffice;    当局部的控制能够处理时,不要使用异常; 
         2. Use the "resource allocation is initialization" technique to manage resources;   使用“资源分配即初始化”技术去管理资源; 
         3. Minimize the use of try-blocks. Use "resource acquisition is initialization" instead of explicit handler code;    尽量少用try-catch语句块,而是使用“资源分配即初始化”技术。 
         4. Throw an exception to indicate failure in a constructor;     如果构造函数内发生错误,通过抛出异常来指明。 
         5. 
Avoid throwing exceptions from destructors;     避免在析构函数中抛出异常。 
         6. 
Keep ordinary code and error-handling code separate;      保持普通程序代码和异常处理代码分开。 
         7. Beware of memory leaks caused by memory allocated by new not being released in case of an exception;  小心通过new分配的内存在发生异常时,可能造成内存泄露。 
         8. 
Assume that every exception that can be thrown by a function will be thrown;    如果一个函数可能抛出某种异常,那么我们调用它时,就要假定它一定会抛出该异常,即要进行处理。 
         9. Don't assume that every exception is derived from class exception;     要记住,不是所有的异常都继承自exception类。 
         10. A library shouldn't unilaterally terminate a program. Instead, throw an exception and let a caller decide;    编写的供别人调用的程序库,不应该结束程序,而应该通过抛出异常,让调用者决定如何处理(因为调用者必须要处理抛出的异常)。 
         11. Develop an error-handling strategy early in a design;    若开发一个项目,那么在设计阶段就要确定“错误处理的策略”。  

C++异常处理机制:保护你的程序不崩溃! 总的来说,C++异常处理机制为我们提供了一种有效的方法来处理程序运行中出现的意外情况,保障了程序的稳定性和健壮性。作为一名合格的程序员,我们需要深入学习和掌握这种机制,以便更好地应对各种异常情况,确保程序的高效、稳定运行。当我们使用try代码块时,它会尝试执行某些操作,如果出现异常情况,就会跳转到catch代码块中执行相应的异常处理。而throw关键字则是用来抛出异常的,它可以将异常信息传递给catch代码块,在那里进行相应的处理。而在catch代码块中,我们会捕获这个异常,并输出错误信息。 阅读详情

相关推荐

error C2039: “int_least8_t”: 不是“`global namespace'”的成员

在向H.265的测试软件HM16.0中配置boost的时候,发现 include &lt;boost/cstdint.hpp&gt;的时候总是会报错:错误 11 error C2039: “int_fast16_t”: 不是“`global namespace'”的成员 D:\Program Files\Caffe\VSContact\boost_1_58_0\boost\cstdint.hpp ...

flyerene的博客 1万+

C++的异常处理

一、什么是异常处理        一句话:异常处理就是处理程序中的错误。二、为什么需要异常处理,以及异常处理的基本思想        C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道怎样处理这些错误,但却

大黑暗天 的专栏 2万+

RFSoC应用XCZU47DR八发八收FPGA设计方案

本文介绍了基于Xilinx XCZU47DR RFSoC芯片的八发八收FPGA设计方案。该方案采用集成射频数据转换器的SoC架构,支持8路14bit ADC(5GSPS)和8路14bit DAC(9.85GSPS),可直接处理5GHz以下射频信号。硬件设计包含高性能输入缓冲器、QMC校正和复杂混频器,适用于5G/6G通信、雷达、卫星通信等领域。文章详细阐述了ADC/DAC子系统参数,包括通道配置、采样率、带宽等特性,并提供了FPGA顶层模块的硬件接口设计,展示了多通道数据收发处理能力。该方案无需外置混频器,

toonyhe的博客 1790

C++ 析构函数中抛出异常是不明智的!

从语法上面讲,析构函数抛出异常是可以的,C++并没有禁止析构函数引发异常,但是C++不推荐这一做法,从析构函数中抛出异常是及其危险的。 析构函数可能在对象正常结束生命周期时调用,也可能在有异常发生时从函数堆栈清理时调用。前一种情况抛出异常不会有无法预料的结果,可以正常捕获;但后一种情况下,因为函数发生了异常而导致函数的局部变量的析构函数被调用,析构函数又抛出异常,本来局部对象抛出的异常应该是由它所在的函数负责捕获的,现在函数既然已经发生了异常,必定不能捕获,因此,异常处理机制只能调用terminate()。

看看世界看看你 996

星光、黑光、全彩夜视摄像头技术解析与实战选型指南

本文深入解析了星光、黑光、全彩与红外等主流夜视摄像头技术的工作原理与核心差异。针对不同监控场景与预算,提供了实战选型指南,帮助用户在安防项目中做出明智决策,实现夜间监控效果最大化。

weixin_29179311的博客 348

【语音识别】基于MFCC特征实现说话人语音识别matlab源码

1 模型 采用能够反映人对语音的感知特性的Mel频率倒谱系数(MFCC)作为特征参数,以及为避免时间规整问题采用矢量量化技术开发的说话人识别系统.MFCC主要的是模拟人耳的听觉过程,相对于其它参数它对语音波形的变化不敏感,更加稳定,系统取得很好的识别结果,实验表明系统训练和识别的计算量和存储量都比较低. 2 部分代码 functionvarargout=Main(varargin) % MAIN M-file for Main.fig % MAIN, by itself...

qq_59747472的博客 848

二十一天学通C++之使用throw抛出异常

使用throw抛出异常本人节选自《21天学通C++》一书    抛出异常(也称为抛弃异常)即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为:throw 表达式;    如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异

博文视点(北京)官方博客 7017

3.7 改造模型:为GPT添加一个分类头(Classification Head)

本文介绍如何改造GPT模型用于文本分类任务。主要内容包括: 分类头设计思路:GPT作为语言模型需要添加分类头才能适应分类任务,类似于为语言大师添加裁判功能。 序列表示选择策略: 分析三种常见方法(CLS标记、平均池化、最后词元表示) 推荐使用最后词元表示,因其最能反映GPT的单向信息流特性 代码实现: 构建GPT2ForSequenceClassification类 包含预训练GPT主体和线性分类头 使用attention_mask智能定位句子结尾 通过高级索引提取最后词元特征 该方法通过简单改造使GPT模

yonggeit的博客 2972

[cpp进阶]C++异常

在实际使用中,我们会自定义异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者没有办法识别异常是从哪一个业务层抛出的,所以实际中都会定义一套继承的规范体系。

weixin_53027918的博客 2967

【华为OD机试真题 新系统】959、 水库溃坝填补 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

【华为OD机试真题 2025C卷】959、 水库溃坝填补 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2025e卷,od机试,2025E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2025华为od,如何备战华

KJ.JK 2942

ArcGIS:景观指数和景观单元关联生成景观指数遥感图像

优快云@_养乐多_本文将记录,使用由 Fragstats V4.2 软件计算的景观指数和瓦片 tif 数据生成景观指数图像的操作步骤。本教程的操作均在ArcGIS上进行,最后生成一个研究区内指定景观单元大小的景观指数图像。结果如下所示,

养乐多的博客 4931

[cpp] 异常

[cpp] 异常 详解

BinaryWiker的博客 1576

模型泛化能力提升:领域自适应与迁移学习

模型泛化能力提升:领域自适应与迁移学习​ ,人工智能,计算机视觉,大模型,AI,在机器学习和深度学习的应用中,模型的泛化能力是一个关键问题。泛化能力指的是模型在新的、未见过的数据上表现良好的能力。然而,实际情况中,训练数据和测试数据往往来自不同的领域,存在分布差异,这会导致模型的泛化能力下降。领域自适应与迁移学习正是为了解决这一问题而发展起来的重要技术。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 3万+

PHP-CPP异常处理机制:从C++到PHP的错误传递终极指南

PHP-CPP是一个强大的C++库,它允许开发者创建高效的PHP扩展,将C++的性能优势与PHP的灵活性完美结合。在开发PHP扩展时,异常处理是确保代码健壮性和可靠性的关键环节。本文将深入探讨PHP-CPP的异常处理机制,从C++异常的抛出到PHP错误的传递,为你提供一套完整的错误处理解决方案。 ## 🧩 PHP-CPP异常体系核心组件 PHP-CPP提供了一套完善的异常处理体系,主要包含以

gitblog_00507的博客 539

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Cpp||异常

C++进阶–>异常 文章目录C++进阶-->异常一:C++异常的概念1.异常二:异常的使用1.异常的抛出和捕获1.1:异常的抛出和匹配原则1.2:在函数调用链中异常栈展开匹配原则2.异常的重新抛出2.1:异常重抛的实例2.2:异常安全2.3:异常规范三:自定义异常体系四:C++标准可的异常体系五:异常的优缺点5.1:异常的优点5.2:异常的缺点 一:C++异常的概念 1.异常 异常是...

Template Code 502

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

10_MFC异常、CPP异常、虚函数的异常、模板类的异常、内存异常

目录 一MFC异常 二CPP异常 三类的异常面向对象的异常 四异常类的派生 五虚函数的异常 六模板类的异常 七内存异常【目录】一、 MFC异常: 2 二、 CPP异常: 3 三、 类的异常(面向对象的异常): 4 1、 例子1: 4 2、 例子2: 5 四、 异常类的派生: 7 五、 虚函数的异常: 8 六、 模板类的异常: 10 七、 内存异常 11一、MFC异常

a2604539133的博客 622

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Cpp异常概述

创建异常类自定义异常通常通过继承标准的exception类来创建。覆盖what()方法,返回描述异常的字符串。抛出自定义异常使用throw关键字,如同使用内置异常一样。捕获自定义异常使用try/catch块捕获自定义异常,与捕获内置异常相同。// 1. 创建自定义异常类public:// 2. 抛出自定义异常try {catch (const DivideByZeroException& e) { // 3. 捕获自定义异常return 0;

Tian Meng的博客 521

Linux版cursor0.51.x内cpp插件功能异常

怀疑是cursor官方停止了对vs老版cpp插件的支持,之前保留的旧版cpp插件功能异常。

urnotuname的博客 1210

C++『异常』

异常处理在软件开发中扮演着关键的角色,它为程序员提供了一种有力的手段来处理和响应程序执行过程中可能出现的错误。本文将深入探讨异常的基本概念、异常处理方式、异常的使用技巧和异常体系的设计,以帮助开发者更好地理解和应用异常处理机制

北 海的博客 2425

CPP学习之异常操作

C++学习之异常操作C++异常栈解旋(unwinding)异常接口声明异常变量的生命周期多态异常标准库异常 C++异常 常见异常:除0溢出,数组下标越界,所读文件步卒不存在,空指针,内存不足等等; C++的异常一旦抛出,如果不捕获,该程序终止; 抛出异常:throw 捕获异常:try….catch 异常严格类型匹配:异常机制和函数机制互不干涉,但捕获方式是通过严格类型匹配。 void th(int a,double b){ if(b==0) throw ex;} int main(){ try{ t

weixin_43654653的博客 543

CPP异常

异常是程序运行时出现的预期之外的情况或错误,导致程序运行终止。int main()cout << "运算结果:" << num3 << endl;return 0;除数为0,程序出现异常,导致程序运行终止。在比如,下面代码,虽然通过检查到了错误,并输出了错误信息,但这并不足以处理错误;特别是对调用者来说无法进行进一步处理。cout << "加载文件失败" << endl;return;

qq_59805046的博客 826

终极指南:如何用backward-cpp快速捕获C++段错误和异常的最佳实践

backward-cpp是一个强大的C++堆栈跟踪美化工具,能够自动捕获段错误和异常,生成清晰易读的错误报告,帮助开发者快速定位问题根源。本文将详细介绍backward-cpp的信号处理机制、安装配置方法以及最佳实践,让你轻松掌握这一必备的C++调试神器。 ## 为什么选择backward-cpp? 在C++开发中,段错误和未捕获的异常常常让开发者头疼不已。传统的调试工具要么输出信息杂乱无章,

gitblog_00936的博客 392

Cpp_异常处理

一、为什么需要异常?1.程序不可能不出错2.传统的错误处理机制存在缺陷 1)通过返回值表示错误 缺点:错误处理流程比较复杂,逐层判断,代码臃肿。 优点:函数调用路径中所有的局部对象(栈对象)都能被右花括号正确地析构,不会内存泄漏。 2)通过远程跳转处理错误优点:不需要逐层判断,一步到位处理错误,代码精炼。 缺点:函数调用路径中的局部对象失去被析构机会,形成内存泄漏。3.C++的异常结合两种

Jerry的专栏 2502

【C++】C++中异常及异常的使用

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了public:, _id(id){}protected:int _id;

zxctsclrjjjcph的博客 1383

71 CPP异常

在try语句块中,如果没有发生异常,执行完try语句块中的代码后,将继续执行try语句块之后的代码;如果发生了异常,用throw抛出异常对象,异常对象的类型决定了应该匹配到哪个catch语句块,如果没有匹配到catch语句块,程序将调用abort()函数中止。如果try语句块中用throw抛出异常对象,并且匹配到了catch语句块,执行完catch语句块中的代码后,将继续执行catch语句块之后的代码,不会回到try语句块中。C++98标准提出了异常规范,目的是为了让使用者知道函数可能会引发哪些异常。

wade1010的专栏 202

yaml-cpp异常安全保证:强异常安全与基本异常安全实现终极指南

yaml-cpp作为一个功能强大的C++ YAML解析器和发射器库,提供了完善的异常安全保证机制。本文将深入探讨yaml-cpp如何通过智能指针管理、RAII模式、noexcept修饰符和精心设计的异常层次结构,为开发者提供可靠的异常安全保证。了解yaml-cpp的异常安全实现对于构建稳定可靠的C++应用程序至关重要。 ## 📊 yaml-cpp异常安全架构概览 yaml-cpp采用了分层异

gitblog_01120的博客 461
上一篇: protocol buffer出现大量链接错误
下一篇: strncpy_s、_strncpy_s_l、wcsncpy_s、_wcsncpy_s_l、_mbsncpy_s、_mbsncpy_s_l
普通网友
博客等级 码龄14年 9粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值