一. 回顾C++异常机制

(一)概述

  1. 异常处理是C++的一项语言机制,用于在程序中处理异常事件(也被称为导常对象)。

  2. 异常事件发生时,使用throw关键字抛出异常表达,抛出点称为异常出现点,由操作系统为程序设置当前异常对象。然后执行程序当前异常处理代码块。

  3. 在包含异常出现点的最内层try块,依次匹配catch语句中的异常对象若匹配成功,则执行catch块内的异常处理语句,然后接着执行try…catch…块之后的代码

  4.如果在当前try…catch…块内找不到匹配该异常对象的catch语句,则由更外层的try…catch…块来处理该异常。如果当前函数的所有try…catch…都不匹配该异常,则递归回退到调用栈的上一层去处理该异常。如果一直退出main()函数都不能处理该异常,则调用系统函数terminate()来终止程序。

(二)异常对象

  1. 异常对象是一种特殊的对象。编译器依据异常抛出表达式构造异常对象(即异常对象总是被拷贝)。对象的类型是由表达式所表示对象的静态编译类型决定的。如Parent& rObj = Child; throw rObj;时会抛出Parent类型的异常对象。

  2. 异常对象存放在内存特殊位置,该位置既不是栈也不是堆,在Windows中是放在线程信息TIB中。该对象由异常机制负责创建和释放!(g++和vc下存储区域处理略有差异)。

  3. 异常对象不同于函数的局部对象,局部对象在函数调用结束后就被自动销毁,而异常对象将驻留在所有可能激活的catch语句都能访问到的内存空间中。当异常对象与catch语句成功匹配后,在该catch语句的结束处被自动析构

  4.在函数中返回局部变量的指针或引用几乎肯定会造成错误。同理,在throw语句中抛出局部变量的指针或引用也几乎是错误的。

【编程实验】捕获异常对象(按值、按引用和按指针)

#include <iostream>
#include <string>
using namespace std; class MyException
{
public:
MyException() { cout << "MyException():" << this << endl; }
MyException(const MyException&) { cout << "MyException(const MyException&):" << this << endl; } ~MyException() { cout << "~MyException():" << this << endl; } void what() { cout << "MyException: this = " << this << endl; }
}; class MyChildExcept : public MyException
{
public:
MyChildExcept() { cout << "MyChildExcept():" << this << endl; }
MyChildExcept(const MyChildExcept&) { cout << "MyChildExcept(const MyChildExcept&):" << this << endl; } ~MyChildExcept() { cout << "~MyChildExcept():" << this << endl; } void what() { cout << "MyChildExcept: this = " << this << endl; }
}; void func_local()
{
// throw 局部对象
MyException localEx;
throw localEx; //尽管localEx是个局部对象,但这里会将其复制构造出一个异常对象,并存储在TIB中。而不是真正的将局部对象抛出去!
} void func_temp()
{
//throw 临时对象
MyException(); //临时对象1
throw MyException(); //编译器会将这个临时对象直接存储在线程TIB中,成为异常对象(注意与临时对象1存储位置一般相距较远!)
} void func_ptr()
{
//throw 指针
throw new MyException(); //注意:异常对象是复制的堆对象而来的指针(存在内存泄漏风险!!!)
} void func_again()
{
MyChildExcept child;
MyException& re = child; //注意抛出的是re的静态类型的异常对象,即MyException,而不是MyChildExcept;
throw re;
} int main()
{
cout << "----------------------------------catch by value------------------------------" << endl;
//按值捕获
try {
func_local(); //throw MyExecption()
}
catch (MyException e) { //复制异常对象,须额外进行一次拷贝!
cout << "catch (MyException e)" << endl;
e.what();
} cout << "--------------------------------catch by reference----------------------------" << endl;
//按引用捕获
try {
func_temp();
}
catch (MyException& e) { //直接引用异常对象,无须拷贝
cout << "catch (MyException& e)" << endl;
e.what();
} cout << "---------------------------------catch by pointer-----------------------------" << endl;
//按指针捕获
try {
func_ptr();
}
catch (MyException* e) { //按指针捕获(可能造成内存泄漏)
cout << "catch (MyException* e)" << endl;
e->what();
delete e; //释放堆对象,防止内存泄漏
} cout << "------------------------------------throw again-------------------------------" << endl;
//二次抛异常
try {
try {
func_again();
}
catch (MyException& e) {
e.what(); //注意以下两种方式不同
//1. 在throw后指定异常对象为e
//throw e; //e会继续复制一份,并抛出复制的异常对象而e本身会被释放! //2.throw后不指定任何对象,只要是在catch中捕获的,一律抛出去。
throw; //此时,e本身再被抛出去。不会另外构造异常对象。
}
}
catch (MyException& e) {
e.what();
} return ;
} /*输出结果(g++编译环境下的输出)
----------------------------------catch by value------------------------------
MyException():0x61fe7f //localEx对象
MyException(const MyException&):0x29978f8 //throw时将localEx复制给异常对象
~MyException():0x61fe7f //释放localEx
MyException(const MyException&):0x61feaf //将异常对象复制给catch中的e对象
catch (MyException e)
MyException: this = 0x61feaf
~MyException():0x61feaf //释放catch中的e对象
~MyException():0x29978f8 //释放异常对象
--------------------------------catch by reference----------------------------
MyException():0x61fe7f //创建临时对象1
~MyException():0x61fe7f //释放临时对象1
MyException():0x29978f8 //throw MyException()时,会将这个临时对象直接创建在TIB中,与临时对象1不在同一地址段中
catch (MyException& e) //按引用传递,e直接引用异常对象,少了一次拷贝
MyException: this = 0x29978f8
~MyException():0x29978f8 //释放异常对象
---------------------------------catch by pointer-----------------------------
MyException():0x299c638 //throw new Exception() ,先在堆中创建一个Exception对象,再将指针throw出去。
catch (MyException* e)
MyException: this = 0x299c638
~MyException():0x299c638 //手动调用delete后,释放堆中的Exception对象。
------------------------------------throw again-------------------------------
MyException():0x61fe7b
MyChildExcept():0x61fe7b
MyException(const MyException&):0x29978f8 //异常对象,这里是re的静态类型,即MyException,而不是MyChildExcept!!!
~MyChildExcept():0x61fe7b
~MyException():0x61fe7b
MyException: this = 0x29978f8 //内层catch到的异常对象
MyException: this = 0x29978f8 //外层catch到的异常对象,注意与内层是同一对象
~MyException():0x29978f8 //释放异常对象
*/

二、异常规格

(一)C++0x与C++11异常规格声明方式的不同

  1. void func() throw() { ... } // throw()声明该函数不会产生异常(C++0x)

  2. void func() throw(int, double) { ... } //可能产生int或double类型异常(C++0x)

  3. void func() noexcept { ... } // noexcept声明该函数不会产生异常(C++11)

  4. void func() noexcept(常量表达式) { ... } //由表达式决定是否产生异常(C++11)

(二)noexcept和throw()异常规格声明的区别

  1. 当函数后面加noexcept和throw()时均表示该函数不会产生异常。

  2. 当使用throw()这种传统风格声明时,如果函数抛出了异常,异常处理机制会进行栈回溯,寻找(一个或多个)catch语句来匹配捕获。如果没有匹配的类型,会调用std::unexcepted函数,但是std::unexcepted本身也可能抛出异常,如果它抛出的异常对当前的异常规格是有效的,异常传递和栈回溯会像以前那样继续进行。这意味着如果使用throw()来声明,编译器几乎没有机会做优化,甚至会产生的代码会很臃肿、庞大。因为:

    ①栈必须保存在回退表中;

    ②所有对象的析构函数必须被正确调用(按照对象构建相反的顺序析构对象)。

    ③编译器可能引入新的传播栅栏(propagation barriers)、引入新的异常表入口,使得异常处理的代码变得庞大。

    ④内联函数的异常规格可能无效。

  3. 当使用noexcept时,std::terminate()函数会被立即调用,而不是调用std::unexcepted()。因此栈不回溯,这为编译器的优化提供了空间。

  4. 总之,如果知道函数绝对不会抛出任何异常,应该使用noexcept,而不是throw() 。

三、noexcept关键字

(一)noexcept异常规格语法

  1. noexcept():声明函数不会抛出任何异常。(注意throw()声明不抛出动态异常时,会保证进行栈回溯,可能调用std::unexcepted)。

  2. noexcept(true)、noexcept(false):前者与noexcept()等价,后者表示函数可能抛出异常。

  3. noexcept(表达式):其中的表达式是可按语境转换为bool类型的常量表达式。若表达式求值为true,则声明函数为不抛出任何异常。若为false则表示函数可能抛出异常。

(二)noexcept在函数指针、虚函数中的使用规则

  1. noexcept与函数指针:

    (1)规则1:如果为函数指针显式声明了noexcept规格,则该指针只能指向带有noexcept的同种规格函数。

    (2)规则2:如果未声明函数指针的异常规则(即隐式说明可能抛异常),则该指针可以指向任何函数(即带noexcept或不带noexcept函数)。

  2. noexcept与虚函数:

    (1)如果基类虚函数声明不抛异常,则子类也必须做出相同的承诺。即子类也须带上noexcept。

    (2)如果基类虚函数声明可能抛异常,则子类可以抛异常,也可以不抛异常。

(三)注意事项

  1. noexcept声明是函数接口的组成部分,这意味着调用方可能会对它有依赖。(但不能依靠noexcept来构成函数的重载)

  2. 相对于不带noexcept声明的函数,带有noexcept声明的函数有更多机会得到优化。但大多数函数是异常中立的,即不具备noexcept性质。

  3. 不抛异常的函数,允许调用潜在抛出异常的函数。异常如果没有被阻止传播,最终会调用std::terminate来终止程序。

  4. noexcept对于移动操作、swap、内存释放函数和析构函数最有价值默认地,operator delete、operator delete[]和析构函数都隐式具备了noexcept性质,但可以重新显式指定为可能抛出异常。

【编程实验】noexcept测试

#include <iostream>
#include <type_traits>
using namespace std; //以下两个函数不能构成重载
//void func(int) noexcept(true){}
//void func(int) noexcept(false){} //1. 异常规格与函数指针
void func1(int) noexcept(true) {}; //不抛出异常
void func2(int) noexcept(false) {}; //可能抛出异常
void func3(int){} //2. noexcept与虚函数
class Parent
{
public:
virtual void func1() noexcept {} //不抛异常
virtual void func2() noexcept(false) {} //可能抛异常
}; class Child : public Parent
{
public:
//基类承诺不抛异常,则子类也必须承诺!
//void func1() {}; //error, Parent::func1() 承诺不抛异常了 //基类声明为可能抛异常,则子类可以抛,也可以不抛异常
void func2() noexcept(true) {} //ok, 子类不抛异常
//void func2() {}; //ok,子类可以抛异常
}; //3. 有条件的noexcept
//3.1 Swap:交互两个数组的元素。
//只要交互元素时不抛异常,但交换整个数组就不抛异常。因此Swap函数是否为noexcept取决于交互元素
template<typename T, size_t N>
void Swap(T(&a)[N], T(&b)[N]) noexcept(noexcept(std::swap(*a, *b))) //*a、*b为首元素
{
for (int i = ; i < N; ++i) {
std::swap(a[i], b[i]);
}
}
template<typename T, size_t N>
void printArray(T(&a)[N])
{
for (int i = ; i < N; ++i) {
cout << a[i] << " ";
}
cout << endl;
} //3.2 func
//func_destructor函数抛不抛异常取决于T的析构函数。如果T析构会抛异常则func_destructor也会抛异常,反之不抛异常。
template<typename T>
void func_destructor(const T&) noexcept(noexcept((std::declval<T>().~T()))) {} struct CA
{
~CA(){ throw ; } //注意析构函数默认为noexcept(true)
}; struct CB
{
~CB()noexcept(false){ throw ; }
}; struct CC
{
CB b;
}; //3.3 pair
template<class T1, class T2>
class MyPair
{
T1 first;
T2 second;
public: //swap函数是否为noexcept,取决于交互first和second的过程是否为noexcept
void swap(MyPair& p) noexcept(noexcept(std::swap(first, p.first)) &&
noexcept(std::swap(second, p.second)))
{
}
}; //4. noexcept
void Throw() { throw ; } //可能抛异常:异常会被向外层传递出去
void NoBlockThrow() { Throw(); } //可能抛异常:异常会被向外层传递出去
void BlockThrow() noexcept { Throw(); } //不抛异常,但此函数实际会抛异常,异常会阻止传递,程序中止 int main()
{
//1. noexcept与函数指针
//1.1规则1:
void(*pf1)(int) noexcept = func1; //正确
//void(*pf2)(int) noexcept = func2; //error;异常规格不同!
//void(*pf3)(int) noexcept = func3; //error,带noexcept的指针,只能指向带同种noexcept规格的函数 //1.2 规则2:
void(*pf4)(int) = func1; //or,pf3未带noexcept,可以指向任何函数
void(*pf5)(int) = func2; //ok
void(*pf6)(int) = func3; //ok //2. noexcept与虚函数(见Child类) //3. 有条件的noexcept //3.1 交换数组
int a[] = { ,,,, };
int b[] = { ,,,, }; Swap(a, b); //Swap函数是否为noexcept,取决于std::swap(*a,*b)函数是否为noexcept
printArray(a);
printArray(b); //3.2 析构函数
try {
CB temp;
func_destructor(temp); //分别测试CA, CB, CC类。此处,由于CB析构函数为可能抛出异常,因此
//抛出会被继续抛出而被catch(...)语句捕获,程序不会被中止
}
catch (...) {
cout << "catch destructor exception" << endl;
} //4. noexcept与异常传递测试
try {
Throw(); //这里可增加测试NoBlockThrow、BlockThrow函数
}catch (...) {
cout <<"catch exception..." << endl;
} return ;
}
/*输出结果:
6 7 8 9 10
1 2 3 4 5
catch destructor exception
catch exception...
*/

第9课 C++异常处理机制的更多相关文章

  1. Java异常处理机制 try-catch-finally 剖析

    Java拥有着强大的异常处理机制,最近初步学习了下,感觉内容还是挺多的,特此来将自己的理解写出来与大家分享. 一. 在Java代码code中,由于使用Myeclipse IDE,可以自动提醒用户哪里有 ...

  2. JAVA 异常处理机制

    主要讲述几点: 一.异常的简介 二.异常处理流程 三.运行时异常和非运行时异常 四.throws和throw关键字 一.异常简介 异常处理是在程序运行之中出现的情况,例如除数为零.异常类(Except ...

  3. 深入理解java异常处理机制

       异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各种不同的 ...

  4. C++学习笔记27:异常处理机制

    一.异常处理机制基础 异常的定义 程序中可以检测的运行不正常的情况 异常处理的基本流程 某段程序代码在执行操作时发生特殊情况,引发一个特定的异常 另一段程序代码捕获该异常并处理它 二.异常的引发 th ...

  5. C++中的异常处理机制

    C++中的捕获异常机制catch参数中实参的类型不同,采取的处理方式则不相同,且与普通的函数调用还不一样,具体表现为当抛出异常throw A()或throw obj时,对象会进行一次额外的对象复制操作 ...

  6. 16、java中的异常处理机制

    异常:就是程序在运行时出现不正常情况.异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述.并封装成对象. 其实就是java对不正常情况进行描述后的对象体现. 对于问题的划 ...

  7. Struts——(四)异常处理机制

    在通常的情况下,我们得到异常以后,需要将页面导航到一个错误提示的页面,提示错误信息.利用Stuts我们可以采用两种方式处理异常: 1.编程式异常处理 即我们在Action中调用业务逻辑层对象的方法时, ...

  8. Java面向对象编程之异常处理机制

    一:Java的异常处理机制的优点: 1:把各种不同情况的异常情况分类,使用JAVA类来表示异常情况,这种类被称为异常类.把各种异常情况表示成异常类,可以充分的发挥类的可扩展性和可重用性. 2:异常流程 ...

  9. Java之异常处理机制

    来源:深入理解java异常处理机制 2.Java异常    异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 ...

随机推荐

  1. .net架构的浅谈

    ,net的架构有以下几种 1.两层架构:UI + 数据层 2.三层架构:UI + 业务层 + 数据层 3.三层 + 接口层 (把相关的业务层抽象成接口,下层来实现接口,中层是依赖) 4.三层 + 接口 ...

  2. 2019 上海轻轻java面试笔试题 (含面试题解析)

      本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.上海轻轻等公司offer,岗位是Java后端开发,因为发展原因最终选择去了上海轻轻,入职一年时间了,也成为了面 ...

  3. 隐马尔科夫模型(Hidden Markov Models) 系列之三

    转自:http://blog.csdn.net/eaglex/article/details/6418219 隐马尔科夫模型(Hidden Markov Models) 定义 隐马尔科夫模型可以用一个 ...

  4. 百度地图API操作实战

    什么是百度地图API: 百度地图API是为开发者免费提供的一套基于百度地图服务的应用接口,包括JavaScript API,web服务API,Android等多种开发工具服务.提供基本地图展现,搜索, ...

  5. 安装Python,输入pip命令报错———pip Fatal error in launcher: Unable to create process using

    今天把Python的安装位置也从C盘剪切到了D盘, 然后修改了Path环境变量中对应的盘符:D:\Python27\;D:\Python27\Scripts; 不管是在哪个目录,Python可以执行了 ...

  6. Ubuntu拒绝root用户ssh远程登录

    sudo vim /etc/ssh/sshd_config 找到并用#注释掉这行:PermitRootLogin prohibit-password 新建一行 添加:PermitRootLogin y ...

  7. <code> 标签 让一段计算机代码显示在网页中

    <code> 标签 解释:要让一段计算机代码显示在网页中,那么这段代码需要用<code> 标签包起来,不然他会被当作网页的代码被 运行. 例如: <code>< ...

  8. shell中sort用法

    1 sort的工作原理 sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出. [rocrocket@rocrocket progr ...

  9. 综合架构之Rsync备份服务,服务端和客户端配置

    服务端配置(即备份服务器) ps:客户端配置见下方 配置一个新服务的步骤: 第一步:先将该服务下载 yum install -y rsync 第二步:编写服务配置文件 配置文件:/etc/rsyncd ...

  10. 大数据技术原理与应用:【第二讲】大数据处理架构Hadoop

    2.1 Hadoop概论 创始人:Doug Cutting 1.简介: 开源免费; 操作简单,极大降低使用的复杂性; Hadoop是Java开发的; 在Hadoop上开发应用支持多种编程语言.不限于J ...