C++异常机制,一篇文章让你了解异常
2017-08-09 10:04
381 查看
C异常机制
异常语法
代码流程
异常说明
构造函数与异常
析构函数与异常
标准异常
•throw 异常对象;
•可以抛出基本类型的对象
–throw -1;
–throw “打开文件失败!”;
•可以抛出类类型的对象
–FileException ex (“打开文件失败!”);
throw ex;
–throw FileException (“打开文件失败!”);
•不要抛出局部对象的指针
–FileException ex (“打开文件失败!”);
throw &ex; // 错误!
二.异常捕获
•try
{
可能引发异常的语句;
}
catch (异常类型1& ex)
{ 针对异常类型1的异常处理; }
catch (异常类型2& ex)
{ 针对异常类型2的异常处理; }
catch (…) { 针对其它异常类型的异常处理; }
•根据异常对象的类型自上至下顺序匹配,而非最优匹配,因此对子类类型异常的捕获不要放在对基类类型异常的捕获后面
•建议在catch子句中使用引用接收异常对象,避免因为拷贝构造带来性能损失,或引发新的异常
–try { … }
catch (FileException& ex) { … }
三.异常对象
•为每一种异常情况定义一种异常类型
–class FileException { … };
–class MemoryException { … };
–class SocketException { … };
•推荐以匿名临时对象的形式抛出异常
–throw FileException (…);
•异常对象必须允许被拷贝构造和析构
•建议从标准库异常中派生自己的异常
•当执行一个throw语句时,运行期异常处理机制会把异常对象复制到一个临时对象中,该临时对象存在于某个“安全区”中
•存储临时异常对象的“安全区”高度平台相关,但可以保证在最后一个使用该异常对象的catch块完成之前,该对象都一直保持可用状态
•当异常发生时,没有什么地方是绝对安全的,除了这个存放临时异常对象的“安全区”,这里是唯一风平浪静的风暴之眼
•由此可见,将一个对象的指针作为异常抛出是不明智的,而通过引用访问“安全区”中的异常对象则是安全的
•两个执行
–函数中throw语句之后的代码执行
–try块中函数调用语句之后的代码执行
•两个不执行
–throw语句不执行
–catch块不执行
二.异常流程
•两个不执行
–函数中throw语句之后的代码不执行
–try块中函数调用语句之后的代码不执行
•两个执行
–throw语句执行
–catch块执行
•异常说明是函数原型的一部分,旨在说明函数可能抛出的异常类型
•异常说明是一种承诺,承诺函数不会抛出异常说明以外的异常类型
–如果函数抛出了异常说明以外的异常类型,那么该异常将无法被捕获,并导致进程中止
•隐式抛出异常的函数也可以列出它的异常说明
•没有异常说明,表示可能抛出任何类型的异常
–void connectServer (char const* config);
•异常说明为空,表示不会抛出任何类型的异常
–void connectServer (char const* config) throw ();
如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比其基类版本抛出更多的异常
•异常说明在函数的声明和定义中必须保持严格一致,否则将导致编译错误
•构造函数可以抛出异常,某些时候还必须抛出异常
–构造过程中可能遇到各种错误,比如内存分配失败
–构造函数没有返回值,无法通过返回值通知调用者
二.不完整对象的回滚
•构造函数抛出异常,对象将被不完整构造,而一个被不完整构造的对象,其析构函数永远不会被执行
–构造函数的回滚机制,可以保证所有成员子对象和基类子对象,在抛出异常的过程中,都能得到正确地析构
–所有动态分配的资源,必须在抛出异常之前,手动释放,否则将形成内存泄漏,除非使用了智能指针
–if (抛出异常的条件满足)
{
释放此前动态分配的资源;
throw 异常对象;
}
•不要从析构函数中主动抛出异常
–在两种情况下,析构函数会被调用
1.正常销毁对象,离开作用域或显式delete
2.在异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统销毁对象
–对于第二种情况,异常正处于激活状态,而析构函数又抛出了异常,并试图将流程移至析构函数之外,这时C++将通过std::terminate()函数,令进程中止
二.尽量内部消化异常
•在析构函数中,执行任何可能引发异常的操作,都尽量把异常在内部消化掉,防止其从析构函数中被继续抛出
异常语法
代码流程
异常说明
构造函数与异常
析构函数与异常
标准异常
C++异常机制
异常语法
一.异常抛出•throw 异常对象;
•可以抛出基本类型的对象
–throw -1;
–throw “打开文件失败!”;
•可以抛出类类型的对象
–FileException ex (“打开文件失败!”);
throw ex;
–throw FileException (“打开文件失败!”);
•不要抛出局部对象的指针
–FileException ex (“打开文件失败!”);
throw &ex; // 错误!
二.异常捕获
•try
{
可能引发异常的语句;
}
catch (异常类型1& ex)
{ 针对异常类型1的异常处理; }
catch (异常类型2& ex)
{ 针对异常类型2的异常处理; }
catch (…) { 针对其它异常类型的异常处理; }
•根据异常对象的类型自上至下顺序匹配,而非最优匹配,因此对子类类型异常的捕获不要放在对基类类型异常的捕获后面
–class GeneralException { ... }; class FileExcept 4000 ion : public GeneralException { ... }; –try { ... } catch (FileException& ex) { ... } catch (GeneralException& ex) { ... }
•建议在catch子句中使用引用接收异常对象,避免因为拷贝构造带来性能损失,或引发新的异常
–try { … }
catch (FileException& ex) { … }
三.异常对象
•为每一种异常情况定义一种异常类型
–class FileException { … };
–class MemoryException { … };
–class SocketException { … };
•推荐以匿名临时对象的形式抛出异常
–throw FileException (…);
•异常对象必须允许被拷贝构造和析构
•建议从标准库异常中派生自己的异常
–#include <stdexcept> –class FileException : public exception { ... };
•当执行一个throw语句时,运行期异常处理机制会把异常对象复制到一个临时对象中,该临时对象存在于某个“安全区”中
•存储临时异常对象的“安全区”高度平台相关,但可以保证在最后一个使用该异常对象的catch块完成之前,该对象都一直保持可用状态
•当异常发生时,没有什么地方是绝对安全的,除了这个存放临时异常对象的“安全区”,这里是唯一风平浪静的风暴之眼
•由此可见,将一个对象的指针作为异常抛出是不明智的,而通过引用访问“安全区”中的异常对象则是安全的
代码流程
一.正常流程•两个执行
–函数中throw语句之后的代码执行
–try块中函数调用语句之后的代码执行
•两个不执行
–throw语句不执行
–catch块不执行
二.异常流程
•两个不执行
–函数中throw语句之后的代码不执行
–try块中函数调用语句之后的代码不执行
•两个执行
–throw语句执行
–catch块执行
异常说明
一.异常说明•异常说明是函数原型的一部分,旨在说明函数可能抛出的异常类型
–返回类型 函数名 (形参表) throw (异常类型1, 异常类型2, ...) { 函数体; } –void connectServer (char const* config) throw (int, string, FileException) { ... }
•异常说明是一种承诺,承诺函数不会抛出异常说明以外的异常类型
–如果函数抛出了异常说明以外的异常类型,那么该异常将无法被捕获,并导致进程中止
•隐式抛出异常的函数也可以列出它的异常说明
–void connectServer (char const* config) throw (int, string, FileException); –void login (char const* username, char const* passwd) throw (int, string, FileExcetion) { connectServer ("/etc/server.cfg"); }
•没有异常说明,表示可能抛出任何类型的异常
–void connectServer (char const* config);
•异常说明为空,表示不会抛出任何类型的异常
–void connectServer (char const* config) throw ();
如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比其基类版本抛出更多的异常
–class A { virtual void foo (void) throw (int, string); virtual void bar (void) throw (); }; –class B : public A { void foo (void) throw (double); // 错误 void bar (void) throw (); };
•异常说明在函数的声明和定义中必须保持严格一致,否则将导致编译错误
构造函数与异常
一.构造函数抛出异常•构造函数可以抛出异常,某些时候还必须抛出异常
–构造过程中可能遇到各种错误,比如内存分配失败
–构造函数没有返回值,无法通过返回值通知调用者
–class String { public: String (char const* str) { if (! (m_str = (char*)malloc (strlen (str ? str : "") + 1))) throw MemoryException (strerror (errno)); ... } }; –try { String str (...); ... } catch (MemoryException& ex) { ... }
二.不完整对象的回滚
•构造函数抛出异常,对象将被不完整构造,而一个被不完整构造的对象,其析构函数永远不会被执行
–构造函数的回滚机制,可以保证所有成员子对象和基类子对象,在抛出异常的过程中,都能得到正确地析构
–所有动态分配的资源,必须在抛出异常之前,手动释放,否则将形成内存泄漏,除非使用了智能指针
–if (抛出异常的条件满足)
{
释放此前动态分配的资源;
throw 异常对象;
}
析构函数与异常
一.析构函数抛出异常•不要从析构函数中主动抛出异常
–在两种情况下,析构函数会被调用
1.正常销毁对象,离开作用域或显式delete
2.在异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统销毁对象
–对于第二种情况,异常正处于激活状态,而析构函数又抛出了异常,并试图将流程移至析构函数之外,这时C++将通过std::terminate()函数,令进程中止
二.尽量内部消化异常
•在析构函数中,执行任何可能引发异常的操作,都尽量把异常在内部消化掉,防止其从析构函数中被继续抛出
–Dummy::~Dummy (void) { try { ... } catch (FileException& ex) { ... } catch (MemoryException& ex) { ... } catch (GeneralException& ex) { ... } catch (runtime_error& ex) { ... } catch (exception& ex) { ... } catch (...) {} }
标准异常
•标准C++库中预定义的异常类型相关文章推荐
- 技术好文:一篇文章带你快速全面了解LoRa!
- 了解HTTP,一篇文章就够了
- [置顶] 一篇文章了解mvc框架工作流程
- 一篇文章快速了解Python的GIL
- 一篇不错的讲解Java异常的文章
- 一篇文章了解互联网从业者
- 一篇文章了解JsBridge
- 一篇不错的讲解Java异常的文章(转载)----感觉很不错,读了以后很有启发
- 一篇不错的讲解Java异常的文章
- 一篇不错的讲解Java异常的文章(转载)----感觉很不错,读了以后很有启发
- [转]一篇不错的讲解Java异常的文章
- 一篇文章让你了解并掌握memcached
- 一篇可以快速了解 极光推送 服务器api 的文章
- grid基础语法介绍(上) 《轴线与网格》里主要讲述了grid与flex中,网格与轴线的基本概念,了解了这些基本概念之后,我们可以更轻松地对布局方式进行研究,这一篇文章主要描述grid布局中,定义在容
- 一篇文章了解相见恨晚的 Android Binder 进程间通讯机制【转】
- 一篇文章了解爬虫技术现状
- 一篇不错的讲解Java异常的文章(转载)----感觉很不错,读了以后很有启发
- 一篇文章全面了解监控知识体系--转
- 一篇不错的讲解Java异常的文章