C与C++中的异常处理(3)

219 篇文章 ¥19.90 ¥99.00
本文详细介绍了C++的异常处理机制,包括基本语法、异常规格声明、标准异常对象类型、异常生命周期以及与其他异常处理机制的对比。通过示例展示了如何使用`catch`, `throw`, `try`等关键字进行异常捕获和抛出。还讨论了C++异常处理的优势,如析构安全性、精确性和可预测性,并预告将深入探讨Microsoft Visual C++的实现细节和限制。" 127472799,15205722,Vue项目搭建与配置指南,"['前端开发', 'Vue.js', 'javascript', 'UI框架']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++异常处理的基本语法和语义
    这次,我来概述标准C++异常处理的基本语法和语义。顺便,我会将它和前两次提到的技术进行比较。(在本文及以后,我将标准C++异常处理简称为EH,将微软的方法称为SEH。)
1.1     基本语法和语义
    EH引入了3个新的C++语言关键字:
l         catch
l         throw
l         try
    异常通过如下语句触发
throw [expression]
    函数通过“异常规格申明”定义它将抛出什么异常:
throw([type-ID-list])
    可选项type-ID-list包含一个或多个类型的名字,以逗号分隔。这些异常靠try块中的异常处理函数进行捕获。
try compound-statement handler-sequence
    处理函数队列包含一个或多个处理函数,形式如下:
catch ( exception-declaration ) compound-statement
    处理函数的“异常申明”指明了这个函数将捕获什么类型的异常。
    和SEH一样,跟在try和catch后面的语句必须刮在{}内,而整个try块组成一条完整的大语句。
    例子:
void f() throw(int, some_class_type)
   {
   int i;
   // ... generate an ´int´ exception
   throw i;
   // ...
   }
 
int main()
   {
   try
      {
      f();
      }
   catch(int e)
      {
      // ... handle ´int´ exception ...
      }
   catch(some_class_type e)
      {
      // ... handle ´some_class_type´ exception ...
      }
   // ... possibly other handlers ...
   return 0;
   }
 
    异常规格申明是EH特有的,SEH和MFC都没有类似的东西。一个空的异常规格申明表明函数不抛出任何异常:
void f() throw()
   {
   // ... function throws no exceptions ...
   }
 
    如果函数没有异常规格申明,它可以抛出任何类型的异常:
void f()
   {
   // ... function can throw anything or nothing ...
   }
 
    当函数抛异常时,关键字throw通常后面带一个被抛出的对象:
throw i;
 
    然而,throw也可以不带对象:
catch(int e)
   {
   // ... handle ´int´ exception ...
   throw;
   }
 
    它的效果是再次抛出当前正被捕获的对象(int  e)。因为空throw的作用是再次抛出已存在的异常对象,所以它必须位于catch语句块中。MFC也有再次抛出异常的功能,SEH则没有,它没有将异常对象交给过处理函数,所以没什么可再次抛出的。
    就象函数原型中的参数申明一样,异常申明也可以是无名的:
catch(char *)
   {
   // ... handle ´char *´ exception ...
   }
    当这个处理函数捕获一个char *型的异常对象时,它不能操作这个对象,因为这个对象没有名字。
    异常申明还可以是这样的特殊形式:
catch(...)
   {
   // ... handle any type of exception ...
   }
 
    就象不定参数中的“...”一样,异常申明中的“...”可以匹配任何异常的类型。
1.2     标准异常对象的类型
    标准库函数可能报告错误。在C标准库中的报错方式在前面说过了。在C++标准库中,有些函数抛出特定的异常,而另外一些根本不抛任何异常。
    因为C++标准中没有明确规定,所以C++的库函数可以抛出任何对象或不抛。但C++标准推荐运行库的实现通过抛出定义在<stdexecpt>中的异常类型或其派生类型来报告错误:
namespace std
   {
   class logic_error;         // : public exception
      class domain_error;     // : public logic_error
      class invalid_argument; // : public logic_error
      class length_error;     // : public logic_error
      class out_of_range;     // : public logic_error
   class runtime_error;       // : public exception
      class range_error;      // : public runtime_error
      class overflow_error;   // : public runtime_error
      class underflow_error;  // : public runtime_error
   }
 
    这些(异常)类只对C++标准库有约束力。在你自己的代码中,你可以抛出(和捕获)任何你所象要的类型。   
1.3     标准中的其它申明
    标准库头文件<exception>申明了几个EH类型和函数
namespace std
   {
   //
   // types
   //
   class bad_exception;
   class exception;
   typedef void (*terminate_handler)();
   typedef void (*unexpected_handler)();
   //
   // functions
   //
   terminate_handler set_terminate(terminate_handler) throw();
   unexpected_handler set_unexpected(unexpected_handler) throw();
   void terminate();
   void unexpected();
   bool uncaught_exception();
   }
 
    提要:
l         exception是所有标准库抛出的异常的基类。
l         uncaught_exception()函数在有异常被抛出却没有被捕获时返回true,其它情况返回false。它类似于SEH的函数AbnormalTermination()。
l         terminate()是EH的应急处理。它在异常处理体系陷入了不可恢复状态时被调用,经常是因为试图重入(在前一个异常正处理过程中又抛了一个异常)。
l         unexpected()在函数抛出一个它没有在“异常规格申明”中申明的异常时被调用。这个预料外的异常可能在退栈过程中被替换为一个bad_excetion对象。
l         运行库提供了缺省terminate_handler()和unexpected_handler() 函数处理对应的情况。你可以通过set_terminate()和set_unexpected()函数替换库的默认版本。
 
1.4     异常生命期
    EH运行于异常生命期的五个阶段:
l         程序或运行库遇到一个错误状况(阶段1)并且抛出一个异常(阶段2)。
l         程序的运行停止于异常点,开始搜索异常处理函数。搜索沿调用栈向上搜索(很象SEH终止异常时的行为)。
l         搜索结束于找到了一个异常申明与异常对象的静态类型相匹配(阶段3)。于是进入相应的异常处理函数。
l         异常处理函数结束后,跳到此异常处理函数所在的try块下面最近的一条语句开始执行(阶段5)。这个行为意味着C++标准中异常总是终止。
    这些步骤演示于这个简单的例子中:
#include <stdio.h>
static void f(int n)
   {
   if (n != 0) // Stage 1
      throw 123; // Stage 2
   }
extern int main()
   {
   try
      {
      f(1);
      printf("resuming, should never appear/n");
      }
   catch(int) // Stage 3
      {
      // Stage 4
      printf("caught ´int´ exception/n");
      }
   catch(char *) // Stage 3
      {
      // Stage 4
      printf("caught ´char *´ exception/n");
      }
   catch(...) // Stage 3
      {
      // Stage 4
      printf("caught typeless exception/n");
      }
   // Stage 5
   printf("terminating, after ´try´ block/n");
   return 0;
   }
/*
   When run yields
      caught ´int´ exception
      terminating, after ´try´ block
*/
 
1.5     基本原理
    C标准库的异常体系处理C++语言时有如下难题:
l         析构函数被忽略。既然C标准库异常体系是为C语言设计的,它们不知道C++的析构函数。尤其,abort()、exit()和longjmp()在退栈或程序终止时不调用局部对象的析构函数。
l         繁琐的。查询全局对象或函数返回值导致了代码混乱-你必须在所有可能发生异常的地方进行明确的异常情况检测,即使是异常情况可能实际上从不发生。因为这种方法是如此繁琐,程序员们可能会故意“忘了”检测异常情况。
l         无弹性的。Longjmp()“抛出”的只能是简单的int型。errno和signal()/raise()只使用了很小的一个值域集合,分辨率很低。Abort()和exit()总是终止程序。Assert()只工作在debug版本中。
l         非固有的。所有的C标准库异常体系都需要运行库的支持,它不是语言内核支持的。
 
    微软特有的异常处理体系也不是没有限制的:
l         SEH异常处理函数不是直接捕获一个异常对象,而是通过查询一个(概念性的)类似errno的全局值来判断什么异常发生了。
l         SEH异常处理函数不能组合,给定try块的唯有的一个处理函数必须在运行期识别和处理所有的异常事件。
l         MFC异常处理函数只能捕获CException及派生类型的指针。
l         通过包含定义了MFC异常处理函数的宏的头文件,程序包含了数百个无关的宏和申明。
l         MFC和SEH都是专属于与Microsoft兼容的开发环境和Windows运行平台的。
 
    标准C++异常处理避免了这些短处:
l         析构安全。在抛异常而进行退栈时,局部对象的析构函数被按正确的顺序调用。
l         不引人注目的。异常的捕获是暗地里的和自动的。程序员无需因错误检测而搞乱设计。
l         精确的。因为几乎任何对象都可以被抛出和捕获,程序员可以控制异常的内容和含义。
l         可伸缩的。每个函数可以有多个try块。每个try块可以有单个或一组处理函数。每个处理函数可以捕获单个类型,一组类型或所有类型的异常。
l         可预测的。函数可以指定它们将抛的异常类型,异常处理函数可以指定它们捕获什么类型的异常。如果程序违反了其申明,标准库将按可预测的、用户定义的方式运行。
l         固有的。EH是C++语言的一部分。你可以定义、throw和catch异常而不需要包含任何库。
l         标准的。EH在所有的标准C++的实现中都可用。
    基于更完备的想法,C++标准委员会考虑过两个EH的设计,在D&E的16章。(For a more complete rationale, including alternative EH designs considered by the C++ Standard´s committee, check out Chapter 16 of the D&E.)
1.6     小结
    下次,我将更深入挖掘EH的语言核心特性和EH的标准库支持。我也将展示Microsoft Visual C++实现EH的内幕。我将开始标志出EH的那些Visual C++只部分支持或完全不支持的特性,并且寻找绕过这些限制的方法。
在我相信设计EH的基本原理是健全的的同时,我也认为EH无意中包含了一些严重的后果。不用责备C++标准的制订者的短视,我理解设计和实现有效的异常处理是多么的难。当我们遭遇到这些无意中的后果时,我将展示它们对你代码的微妙影响,并且推荐一些技巧来减轻其影响。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值