C++异常处理机制详解



异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况(异常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第三版

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/352989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Github pull request 工作流总结

github 上面有很多非常不错的开源项目&#xff0c;我们也可以向其贡献自己的代码&#xff0c;那么我们如何提交自己的代码给开源项目呢&#xff1f;这里就要用到 pull request 的提交方式。当然&#xff0c;基于 git 的其他平台也是类似的用法。 假设源仓库为&#xff1a;https…

[MEGA DEAL] Java编程-硕士课程(85%折扣)

获得有关Java所有事物的高级分步指导 嘿&#xff0c;怪胎&#xff0c; 本周&#xff0c;在我们的JCG Deals商店中 &#xff0c;我们提供了一个极端的报价 。 我们提供的Java编程–硕士课程 仅售29美元&#xff0c;而不是原始价格149美元 &#xff0c;是的&#xff0c;可享受…

java中对象别名使用_JAVA中的别名现象

问题的提出&#xff1a;在java中&#xff0c;对基本数据类型的赋值时&#xff0c;是将数据从一个地方复制到另外一个地方&#xff0c;当ab时&#xff0c;将b的内容复制给a,若修改a时&#xff0c;b并不会受到这种修改的影响。在对对象进行赋值时&#xff0c;当我们对一个对象进行…

C/C++中的运算符优先级总结

C语言中的运算符 说明 运算符 结合性 初等运算符 () [] -> . -> 单目运算符 ! ~ -- - (类型) * & sizeof <- 算术运算符 * / % -> 算术运算符 - -> 移位运算符 << >> -> 关系运算符 > > < < -> 关系运算符 ! -> 按位与…

nginx 直接在配置文章中设置日志分割

直接在nginx配置文件中&#xff0c;配置日志循环&#xff0c;而不需使用logrotate或配置cron任务。需要使用到$time_iso8601 内嵌变量来获取时间。$time_iso8601格式如下&#xff1a;2015-08-07T18:12:0202:00。然后使用正则表达式来获取所需时间的数据。 按天分割日志 使用下面…

javaone_JavaOne 2012:JavaOne技术主题演讲

javaoneMark Reinhold从JavaOne 2012技术主题演讲开始。 他说&#xff0c;今年的版本将有所不同&#xff0c;因为它将使用大致相同的示例来说明Java的各个方面&#xff0c;而不是对Java的每个组件进行单独的单独介绍。 JavaFX团队的Richard Bair和Jasper Potts &#xff08;并与…

java sqlserver 死锁_sqlserver数据库发生死锁处理

SQLSERVER数据库锁表1. 查看被锁的表select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableNamefrom sys.dm_tran_locks where resource_typeOBJECT2. 解除表的锁定declare spid intSet spid 57 --锁表进程declare sql varchar(1000)set sqlk…

setjump和longjump

int setjmp( jmp_buf env );void longjmp( jmp_buf env, int value );# setjmp(j)设置“jump”点&#xff0c;用正确的程序上下文填充jmp_buf 对象j。这个上下文包括程序存放位置、栈和框架指针&#xff0c;其它重要的寄存器和内存数据。当初始化完jump 的上下文&#xff0c;se…

jmeter操作数据库

1) jmeter不能直接连数据库&#xff0c;需要先添加jar包。 然后将jar包的路径添加到下图&#xff1a; 2) 操作数据库之前要知道数据库的信息&#xff08;ip、端口号、账号、密码&#xff09;&#xff0c;操作哪个数据库就连哪个&#xff1a; 在配置元件-JDBC Connectio…

使用Hystrix DSL创建弹性骆驼应用程序

Apache Camel是一个成熟的集成库&#xff08;到现在已有9年的历史了&#xff09;&#xff0c;它实现了Enterprise Integration Patterns一书中的所有模式。 但是Camel不仅是EIP实现库&#xff0c;它还是一个不断发展&#xff0c;添加新模式并适应行业变化的现代框架。 除了在每…

php7 对象转数组,php7中为对象/关联数组进行解构赋值

在CoffeeScript&#xff0c;Clojure&#xff0c;ES6和许多其他语言中&#xff0c;我们对对象/贴图/等进行了解构&#xff0c;如下所示&#xff1a;obj {keyA: Hello from A, keyB: Hello from B}{keyA, keyB} obj我在php中找到了这个list函数&#xff0c;可以让你像这样构造数…

虚函数表

虚函数   C中的虚函数的实现一般是通过虚函数表(C规范并没有规定具体用哪种方法&#xff0c;但大部分的编译器厂商都选择此方法)。 类的虚函数表是一块连续的内存&#xff0c;每个内存单元中记录一个JMP指令的地址。 注意的是&#xff0c;编译器会为每个有虚函数的类创建一个…

linux bash tutorial

bash read-special-keys-in-bash xdotool linux 登录启动顺序转载于:https://www.cnblogs.com/shaohef/p/9528927.html

centos7安装php8,centos8安装php7.4

一&#xff0c;下载php7.41&#xff0c;官方网站:https://www.php.net/2,下载[rootyjweb source]# wget https://www.php.net/distributions/php-7.4.2.tar.gz说明&#xff1a;在linux上以编译方式安装软件时&#xff0c;多数人都习惯把软件安装到 /usr/local目录下&#xff0c…

C++的四种强制类型转换

C的四种强制类型转换&#xff0c;所以C不是类型安全的。分别为&#xff1a;static_cast , dynamic_cast , const_cast , reinterpret_cast 为什么使用C风格的强制转换可以把想要的任何东西转换成合乎心意的类型。那为什么还需要一个新的C类型的强制转换呢&#xff1f; 新类型的…

MongoDB系列之——安装和启动

CentOS 7 安装MongoDB 4.0 社区版 1. Yum安装 创建Yum仓库先创建新的文件 vim /etc/yum.repos.d/mongodb-org-4.0.repo  在新文件中填入     [mongodb-org-4.0] nameMongoDB Repository baseurlhttps://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.0/x86_64/…

javaone_JavaOne 2012:JavaFX图形技巧和窍门

javaone我回到希尔顿&#xff08;皇家宴会厅B&#xff09;看了Richard Bair的&#xff08;Oracle Java Client Architect&#xff09;的“ JavaFX图形技巧和窍门”。 Bair与FX Experience相关联&#xff0c;并且显然了解JavaFX。 拜尔说&#xff0c;他演讲的主题是表演。 他告诫…

php pdo 参数绑定,PDO预处理之参数绑定和列绑定

摘要&#xff1a;PDO查询中&#xff0c;2个绑定操作&#xff1a;参数绑定与列绑定&#xff1b;参数绑定&#xff1a;bindParm() 和 bindValue();bindParm(:占位符,变量,类型常量) 类型常量默认为字符串bindValue(:占位符,值或变量,类型常量) 如果直接传值&#xff0c;可省略类型…

自定义C++异常处理

例1&#xff1a;自定义一个继承自excepton的异常类myException C标准中&#xff0c;定义在<stdexcept>中的任何异常类都派生自exception Class&#xff0c;本例也只是简单地由exception继承&#xff0c;在try段抛出一个异常并捕捉。代码如下&#xff1a; /* test.cpp ve…

DCL并非单例模式专用

我相信大家都很熟悉DCL&#xff0c;对于缺少实践经验的程序开发人员来说&#xff0c;DCL的学习基本限制在单例模式&#xff0c;但我发现在高并发场景中会经常遇到需要用到DCL的场景&#xff0c;但并非用做单例模式&#xff0c;其实DCL的核心思想和CopyOnWrite很相似&#xff0c…