异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况(异常exception)时相互通信的机制。本文总结了19个C++异常处理中的常见问题,基本涵盖了一般C++程序开发所需的关于异常处理部分的细节。

1. throw可以抛出哪些种类的异常对象?如何捕获?

1)异常对象通常是一个class对象, 通常用以下代码抛出:

// 调用的类的构造函数

throw popOnEmpty();

但是throw 表达式也可以抛出任何类型的对象, 例如(虽然很不常见)在下面的代码例子中,函数mathFunc()抛出一个枚举类型的异常对象

enum EHstate { noErr, zeroOp, negativeOp, severeError };

int mathFunc( int i )

{

if ( i == 0 )

throw zeroOp; // 枚举类型的异常

}

2)抛出异常的语句或其调用函数要在try块中才能被捕获。

2. catch子句的语法

一个catch 子句由三部分构成:

1)关键字catch

2)异常声明,在括号中的单个类型或单个对象声明被(称作异常声明,exception declaration)

3)复合语句中的一组语句。

// stackExcp.h

class popOnEmpty { };

class popOnFull { };

catch ( pushOnFull )

{

cerr << "trying to push a value on a full stack\n";

return errorCode88;

}

3. 异常声明可以只是一个类型声明而不是对象声明吗?

catch 子句的异常声明可以是一个类型声明或一个对象声明。当我们要获得throw 表达式的值或者要操纵throw 表达式所创建的异常对象时,我们应该声明一个对象。

catch ( pushOnFull eObj )

{

cerr << "trying to push the value " << eObj.value() << " on a full stack\n";

}

4. 异常声明中异常对象的拷贝过程?

catch 子句异常声明的行为特别像参数声明。同理,也可以分出按值传递和引用传递(指针)。通常采用的是引用传递。

例1:按值传递。当进入catch 子句时,如果异常声明声明了一个对象,则用该异常对象的拷贝初始化这个对象。例中对象eObj 是用异常对象的值来初始化的,会调用拷贝构造函数。

void calculate( int op ) {

try {

mathFunc( op );

}

catch (pushOnFull eObj ) {

// eObj 是被抛出的异常对象的拷贝

}

}

例2:引用传递。catch子句可以直接引用由throw 表达式创建的异常对象,而不是创建一个局部拷贝。可以防止不必要地拷贝大型类对象。

void calculate( int op ) {

try {

mathFunc( op );

}

catch (pushOnFull &eObj ) {

// eObj 引用了被抛出的异常对象

}

}

5. 异常处理的栈展开过程是什么?

在查找用来处理被抛出异常的catch 子句时,因为异常而退出复合语句和函数定义,这个过程被称作栈展开(stack unwinding)。随着栈的展开,在退出的复合语句和函数定义中声明的局部变量的生命期也结束了。C++保证,随着栈的展开,尽管局部类对象的生命期是因为抛出异常而被结束,但是这些局部类对象的析构函数也会被调用。

6. 异常抛出没有在try块中或抛出的异常没有对应的catch语句来捕捉,结果如何?

异常不能够保持在未被处理的状态,异常对于一个程序非常重要,它表示程序不能够继续正常执行。如果没有找到处理代码,程序就调用C++标准库中定义的函数terminate()。terminate()的缺省行为是调用abort() ,指示从程序非正常退出。

7.为什么要重新抛出异常?怎么写?

在异常处理过程中也可能存在“单个catch 子句不能完全处理异常”的情况。在对异常对象进行修改或增加某些信息之后,catch 子句可能决定该异常必须由函数调用链中更上级的函数来处理。表达式的形式为:throw;

例子如下:

try

{

entryDescr->checkMandatoryData(beModel_);

}

catch (CatchableOAMexception & error) // 只能用引用声明

{

vector<string> paramList;

paramList.push_back(currentDn);

error.addFrameToEnd(6,paramList);  // 修改异常对象

throw;  //重新抛出异常, 并由另一个catch 子句来处理

}

注意1:被重新抛出的异常就是原来的异常对象,所以异常声明一定要用引用。

注意2:在catch 语句里也可以抛出其它

8. 怎么捕捉全部异常或未知异常?

可以用catch ( ... ) { } 。

作用在于:1. 可以释放在前面获得的资源(如动态内存),因为异常退出,这些资源为释放。2. 捕获其余类型的未知异常。

catch 子句被检查的顺序与它们在try 块之后出现的顺序相同。一旦找到了一个匹配,则后续的catch 子句将不再检查。这意味着如果catch(...)与其他catch 子句联合使用,它必须总是被放在异常处理代码表的最后,否则就会产生一个编译时刻错误。例子如下:

catch ( pushOnFull ) {}

catch ( popOnEmpty ) { }

catch (...) { } // 必须是最后一个catch 子句

9. 为什么 catch 子句的异常声明通常被声明为引用?

1)可以避免由异常对象到 catch 子句中的对象的拷贝,特别是对象比较大时。

2)能确保catch子句对异常对象的修改能再次抛出。

3)确保能正确地调用与异常类型相关联的虚拟函数,避免对象切割。

具体参见4,7,17。

10. 异常对象的生命周期?

产生:throw className()时产生。

销毁:该异常的最后一个catch 子句退出时销毁

注意:因为异常可能在catch子句中被重新抛出,所以在到达最后一个处理该异常的catch 子句之前,异常对象是不能被销毁的。

11. const char *到char * 非法的异常类型转换。

我们注意到下面的代码在VC中可以正常运行(gcc不能)。

try { throw "exception";}

catch (char *) {cout << "exception catch!" <<endl;}

实际上throw的是一个const char *, catch的时候转型成char *。这是C++对C的向下兼容。

同样的问题存在于:

1. char *p =  “test”; // 也是一个const char * 到char *转型。

2. void func(char* p) { printf("%s\n", p); }

func("abc"); // const char * 到char *

以上两例在编译时不警告,运行时不出错,是存在隐患的。

12. 异常规范(exception specification)的概念?

异常规范在函数声明是规定了函数可以抛出且只能抛出哪些异常。空的异常规范保证函数不会抛出任何异常。如果一个函数声明没有指定异常规范,则该函数可以抛出任何类型的异常。

例1:函数Pop若有异常,只能抛出popOnEmpty和string类型的异常对象

void pop( int &value ) throw(popOnEmpty, string);

例2:函数no_problem()保证不会抛出任何异常

extern void no_problem() throw();

例3:函数problem()可以抛出任何类型的异常

extern void problem();

13. 函数指针的异常规范?

我们也可以在函数指针的声明处给出一个异常规范。例如:

void (*pf) (int) throw(string);

当带有异常规范的函数指针被初始化或被赋值时,用作初始值或右值的指针异常规范必须与被初始化或赋值的指针异常规范一样或更严格。例如:

void recoup( int, int ) throw(exceptionType);

void no_problem() throw();

void doit( int, int ) throw(string, exceptionType);

// ok: recoup() 与 pf1 的异常规范一样严格

void (*pf1)( int, int ) throw(exceptionType) = &recoup;

// ok: no_problem() 比 pf2 更严格

void (*pf2)() throw(string) = &no_problem;

// 错误: doit()没有 pf3 严格

void (*pf3)( int, int ) throw(string) = &doit;

注:在VC和gcc上测试失败。

14. 派生类中虚函数的异常规范的声明?

基类中虚拟函数的异常规范,可以与派生类改写的成员函数的异常规范不同。但是派生类虚拟函数的异常规范必须与基类虚拟函数的异常规范一样或者更严格。

class Base {

public:

virtual double f1( double ) throw ();

virtual int f2( int ) throw ( int );

virtual string f3( ) throw ( int, string );

// ...

};

class Derived : public Base {

public:

// error: 异常规范没有 base::f1() 的严格

double f1( double ) throw ( string );

// ok: 与 base::f2() 相同的异常规范

int f2( int ) throw ( int );

// ok: 派生 f3() 更严格

string f3( ) throw ( int );

// ...

};

15. 被抛出的异常的类型和异常规范中指定的类型能进行类型转换吗?

int convert( int parm ) throw(string)

{

if ( somethingRather )

// 程序错误:

// convert() 不允许 const char* 型的异常

throw "help!";

}

throw 表达式抛出一个C 风格的字符串,由这个throw 表达式创建的异常对象的类型为const char*。通常,const char*型的表达式可以被转换成string 类型。但是,异常规范不允许从被抛出的异常类型到异常规范指定的类型之问的转换。

注意:

当异常规范指定一个类类型(类类型的指针)时,如果一个异常规范指定了一个类,则该函数可以抛出“从该类公有派生的类类型”的异常对象。类指针同理。

例如:

class popOnEmpty : public stackExcp { };

void stackManip() throw( stackExcp )  // 异常规范是stackExcp类型

{

throw stackExcp();            // 与异常规范一样

throw popOnEmpty ();      // ok. 是stackExcp的派生类

}

16. 公有基类的catch子句可以捕捉到其派生类的异常对象。

int main( ) {

try {

// 抛出pushOnFull异常

}

catch ( Excp ) {

// 处理 popOnEmpty 和 pushOnFull 异常

throw;

}

catch ( pushOnFull ) {

// 处理 pushOnFull 异常

}

}

在上例中,进入catch ( Excp )子句,重新抛出的异常任然是pushOnFull类型的异常对象,而不会是其基类对象Excp。

17. 异常对象中怎么运用虚拟函数来完成多态?

1)异常申明是对象(不是引用或指针),类似于普通的函数调用,发生对象切割。

// 定义了虚拟函数的新类定义

class Excp {

public:

virtual void print() {

cerr << "An exception has occurred"

<< endl;

}

};

class stackExcp : public Excp { };

class pushOnFull : public stackExcp {

public:

virtual void print() {

cerr << "trying to push the value " << _value

<< " on a full stack\n";

}

// ...

};

int main( ) {

try {

// iStack::push() throws a pushOnFull exception

} catch ( Excp eObj ) {

eobj.print(); // 调用虚拟函数

// 喔! 调用基类实例

}

}

对象切割过程:eObj 以“异常对象的基类子对象Excp 的一个拷贝”作为初始值,eobj 是Excp 类型的对象,而不是pushOnFull 类型的对象。

输出结果:

An exception has occurred

2)异常声明是一个指针或引用

int main( ) {

try {

// iStack::push() 抛出一个 pushOnFull 异常

}

catch ( Excp &eObj ) {

eobj.print(); // 调用虚拟函数 pushOnFull::print()

}

}

输出结果:

trying to push the value 879 on a full stack

18. function try block(函数try块)

把整个函数体包含在一个try块中

int main()

try {

// main() 的函数体

}

catch ( pushOnFull ) {

// ...

}

catch ( popOnEmpty ) {

// ...

}

19. 为什么类的构造函数需要函数try块?

如下例,普通的try块

inline Account::无法处理成员初始化表中的异常,若serviceCharge抛出异常,则这个异常无法被捕捉到。

Account( const char* name, double opening_bal )

: _balance( opening_bal - serviceCharge() )

{

try {

_name = new char[ strlen(name)+1 ];

strcpy( _name, name );

_acct_nmbr = get_unique_acct_nmbr();

}

catch ( ...) {

// 特殊处理

// 不能捕获来自成员初始化表的异常

}

}

改进后如下,使用函数try 块是保证“在构造函数中捕获所有在对象构造期间抛出的异常”的惟一解决方案。关键字try 应该被放在成员初始化表之前,try 块的复合语句包围了构造函数体。

inline Account::

Account( const char* name, double opening_bal )

try

: _balance( opening_bal - serviceCharge() )

{

_name = new char[ strlen(name)+1 ];

strcpy( _name, name );

_acct_nmbr = get_unique_acct_nmbr();

}

catch ( ... )

{

// 特殊处理

// 现在能够捕获来自 ServiceCharge() 的异常了

}

参考文献:

C++ Primer第三版

C++异常处理机制详解相关推荐

  1. java异常处理机制详解

    java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.

  2. SpringMVC异常处理机制详解[附带源码分析]

    SpringMVC异常处理机制详解[附带源码分析] 参考文章: (1)SpringMVC异常处理机制详解[附带源码分析] (2)https://www.cnblogs.com/fangjian0423 ...

  3. Java------IO流与异常处理机制 详解

    IO流与异常处理机制 File类 File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径) 使用File可以做到: 1:访问其表示的文件或目录的属性信息,例如:名 ...

  4. SpringBoot异常处理ErrorController详解

    文章目录 一.背景 二.SpringBoot的默认异常处理BasicErrorController 三.自定义错误异常 写在前面: 我是「境里婆娑」.我还是从前那个少年,没有一丝丝改变,时间只不过是考 ...

  5. php7自定义异常处理,基于PHP7错误处理与异常处理方法(详解)

    PHP7错误处理 PHP 7 改变了大多数错误的报告方式.不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为 Error 异常抛出. 这种 Error 异常可以像 Exception 异常一 ...

  6. python语言程序的特点_Python语言概述及其运行机制详解

    即日起,我们将打开一个新的编程世界的大门--Python语言.Python是一种跨平台的计算机程序设计语言.是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新 ...

  7. Python语言概述及其运行机制详解

    即日起,我们将打开一个新的编程世界的大门--Python语言.Python是一种跨平台的计算机程序设计语言.是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新 ...

  8. PHP autoload机制详解

    PHP autoload机制详解 转载自 jeakccc PHP autoload机制详解 (1) autoload机制概述 在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个 ...

  9. 模糊匹配 读音_onenote搜索机制详解②:两种搜索模式,模糊与精确匹配

    先从纯文本搜索讲起,这是最基本也是最重要的. 从这篇开始,以及接下来连续几篇文章,都会介绍搜索的基础功能.注意,这几篇文章中谈论的都是基本的.正常的搜索功能,暂时不考虑Bug等因素. 在很多软件(例如 ...

最新文章

  1. 六一欢乐赛 考试总结
  2. PHP MySql数据库访问
  3. 神策数据入选“2021CCFA 零售行业技术新锐企业榜单”
  4. AOP in Asp.net MVC
  5. 直播预告 | 旷视科技李彦玮:动态网络及其在场景分割中的应用
  6. Android之error: ‘const struct JNINativeInterface‘ has no member named ‘callVoidMethod‘
  7. linux下的静态库与动态库
  8. 成为优秀的Java程序员要具备哪些技能?
  9. 数据库之SqlDataAdapter
  10. 4岁的拼多多超越20岁的百度,成为中国第五大互联网公司!
  11. 多媒体计算机技术的核心技术是,多媒体计算机技术与教学
  12. Android-传感器开发
  13. Java 编程题自动评分技术的研究与实现(一)
  14. NLifeBill第四章添加页面
  15. 新注册Apple ID无法登陆 Apple Store,出现安全性提示
  16. Harbor: Harbor卸载安装及基本使用教程
  17. VSCode调试代码的三种方式
  18. 学习ROS常用的官方网站,学习资源整理
  19. 设置silverlight启用剪贴板的功能
  20. 三维电子沙盘卫星图片矢量地图高程数据来源

热门文章

  1. 4g的服务器mysql配置文件,服务器物理内存16G mysql数据库my.cnf配置及参数说明
  2. oracle怎么格式化sql语句,Oracle sqlplus格式化数据
  3. 视频光端机园区出入口监控项目应用方案详解
  4. 飞畅科技-交换机的三种交换方式详解
  5. 高速连传与LORA的区别和优势
  6. python参数化建模 书_Python 中如何实现参数化测试?
  7. linux可以http安装么,Linux 5下 http的安装
  8. 单片机float数发给上位机_上位机倒计时器
  9. Paw 百度ai_直面落地!百度EasyDL产业智能创新大赛成果覆盖能源、交通、水利民生重业...
  10. [精品]CSAPP Bomb Lab 解题报告(七)——隐藏关卡