文章目录
异常提供了处理特殊情况的机制,一般都是运行过程中出错,比如程序试图打开一个不可用的文件,请求过多的内存,或者遇到不能容忍的值(比如被0除时,其实不允许这个操作,有的编译器会生成一个表示无穷大的浮点数,称为inf,有的编译器则会直接崩溃),如果不处理这些特殊情况,程序会终止。所以我们必须要预防这些情况。
异常很新(但是C99之前就有了,不是C++11新增的),有的编译器不支持,有的编译器默认关闭这个功能。
如果不用异常机制,怎么处理这些特殊情况
没有发明出异常机制之前,对这些情况不可能就完全置之不理那,也是有几个办法的。
使用abort函数,遇到这些情况(自己分析清楚并用if捕捉)就中止程序
abort()
函数的原型在cstdlib
或者stdlib.h
中,它会向标准错误流cerr发送程序异常终止的消息,abnormal program termination,然后终止程序,且会返回一个随实现而异的值(不同编译器厂商的实现有所不同)以告诉调用程序(可能是操作系统或者其他父进程)处理失败。
abort()函数是否刷新文件缓冲区也是取决于实现的。exit()函数会刷新文件缓冲区,即用于存储读写到文件的数据的内存区域。
示例:求调和均值
//error1.cpp -- using abort()
#include <iostream>
#include <cstdlib>
double hmean(double a, double b);//求调和均值
int main()
{
using std::cout;
double x, y, z;
cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
z = hmean(x, y);
cout << "Harmonic mean of " << x << " and "
<< y << " is " << z << std::endl;
cout << "Enter two numbers: ";
}
cout << "Bye!\n";
return 0;
}
double hmean(double x, double y)
{
if (x == -y)
{
std::cout << "Untenable arguments to hmean()\n";
std::abort();
}
return 2.0 * x * y / (x + y);
}
从输出看到,main程序返回给操作系统的返回码不再是0 ,而是3,代表程序并未完美执行,而是运行过程中就中止了。
因为abort函数直接在main调用的子函数里面就终止了,根本没有回到main
Enter two numbers: 2.0 5.2
Harmonic mean of 2 and 5.2 is 2.88889
Enter two numbers: 5.0 -5.0
Untenable arguments to hmean()
Process returned 3 (0x3) execution time : 23.203 s
Press any key to continue.
返回错误码或者用bool类型作为返回值以说明函数操作是否成功
error code
示例:还求调和均值
//error2.cpp -- returning an error code
#include <iostream>
#include <cfloat> //(or float.h) for DBL_MAX
bool hmean(double a, double b, double * ans);//求调和均值,ans用来放答案
//返回值为false表示遇到意外没有完成计算任务
int main()
{
using std::cout;
double x, y, ans;
cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
if (hmean(x, y, &ans))
cout << "Harmonic mean of " << x << " and "
<< y << " is " << ans << std::endl;
else
cout << "One value should not be the negative "
<< "of the other - try again!\n";
cout << "Enter two numbers: ";
}
cout << "Bye!\n";
return 0;
}
bool hmean(double x, double y, double * ans)
{
if (x == -y)
{
*ans = DBL_MAX;
return false;
}
*ans = 2.0 * x * y / (x + y);
return true;
}
Enter two numbers: 2 -2
One value should not be the negative of the other - try again!
Enter two numbers: 2 3
Harmonic mean of 2 and 3 is 2.4
Enter two numbers: a
Bye!
其实除了用一个指针或引用参数作为答案,还可以用全局变量,但是显然后者不安全,万一别人把全局变量改了呢
C++的异常机制:对运行时错误进行响应的机制,把程序控制权转移
异常的处理机制分为三大块:
其中每一部分都涉及一个关键字,分别是throw, catch, try。三个关键字协同工作,保证异常机制的正常运行。
三个关键字的出现次序是:try–throw–catch。从try内的代码开始执行,到一个异常被throw出来,于是步步按照栈回退,又回到某一个try(栈内倒着第一个),然后找这个try后面的catch,如果没找到,则继续回退找上一个try,并在这个新try后面找catch,直到找到匹配的catch把异常处理掉。如果没找到,就会调用terminate函数,terminate函数在调用abort函数。
设计异常机制的目的:为设计容错程序提供语言级支持
异常就是为了帮助程序员灵活地处理程序可能出现的错误的。是为了更好的容错。
C++为了更好地处理可能出现的错误,以及标准化处理方法,就提出了异常机制,并且把这种机制完全融合到了C++语言中:
- 在头文件exception.h中定义了exception类,通常把exception类作为其他异常类的基类
- exception类中设计了一个叫做==what()==的虚方法(虚拟成员函数),它返回一个字符串。由于它是虚方法,所以可以在exception的派生类中重新定义。
#include <exception>
class bad_hmean : public std::exception
{
public:
const char * what() {return "bad arguments to hmean()";}
};
class bad_gmean : public std::exception
{
public:
const char * what() {return "bad arguments to gmean()";};
};
try{
···
}
catch (std::exception & e){
cout << e.what() << endl;
}
引发异常:throw关键字,实现跳转
throw语句实际上就是跳转语句。他后面的字符或者字符串指出了异常的特征或者类型。
return,continue,break, throw,不常用的goto。都是用于跳转的,命令程序直接调到另一个地址处存放的代码去执行。
异常的类型一般是类类型的对象,比如string类的字符串对象,或者其他类类型的对象。
throw语句很像返回return语句,因为它也会终止函数的执行,但和return不同的是,它并不把控制权还给主调函数,而是让程序沿着调用函数的顺序倒退,直到找到在try块里的函数,这个函数有可能是调用函数也可能不是(后面会仔细说明)。程序会在拿到控制权的函数里找和引发的异常类型匹配的异常处理程序,即try块后面的catch块。
捕获异常:catch关键字开头的异常处理程序(catch块)
catch block
异常处理程序exception handler,即catch块。
catch块有点像函数定义,因为catch后面就是一对圆括号和一对大括号。但他当然不是函数定义,只是长得像。
关键字catch表示这是一个异常处理程序,编译器一看到catch关键字就知道这不是函数了。
圆括号里的参数类型可以是char *
等,表示这个处理程序和字符串异常(throw后面跟的是字符串则为字符串异常)匹配。
只要引发的异常和catch块的参数类型匹配了,就会执行catch块内部的代码。
如果引发的异常找不到参数匹配的catch块,则程序最终就只好调用abort函数了。即abort是终极屏障,下下选,实在没人接锅了就只能终止收场了。
try块:把可能引发异常的代码放在里面
try block
把可能引发异常的代码放在try块里面(放在外面则无法被catch块捕捉),一旦出现异常,就会被try块后面的catch块捕捉并处理。
如果执行完try块的语句后,并没有引发异常,那么程序就会跳过后面的catch块们,直接处理catch块后面的第一条语句。
示例1:还是调和均值
//error3.cpp -- using an exception
#include <iostream>
double hmean(double a, double b);//求调和均值
int main()
{
using std::cout;
double x, y, ans;
cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
try
{
ans = hmean(x, y);
}
catch (const char * s)
{
cout << s << std::endl;//首先打印出我捕获到的异常
cout << "Enter a new pair of numbers: ";
continue;//命令程序跳过while剩余部分,调到while起始位置去
}
cout << "Harmonic mean of " << x << " and "
<< y << " is " << ans << std::endl;
cout << "Enter next set of numbers<q to quit>: ";
}
cout << "Bye!\n";
return 0;
}
double hmean(double x, double y)
{
if (x == -y)
throw "bad hmean() arguments: x = - y not allowed!";
return 2.0 * x * y / (x + y);
}
hmean函数中的if语句,如果引发异常,throw执行后,就结束了hmean函数的执行,程序在栈里向后搜索,发现是main()中的try块调用的hmean函数,所以程序就去找这个try后面和自己异常类型匹配的catch块,这里throw后面是字符串,刚好和唯一一个catch块的参数匹配了,char指针,于是程序就把throw后面的字符串"bad hmean() arguments: x = - y not allowed!"
赋给catch块的s,然后进入到catch块内部执行代码,才打印出来了这个字符串。
Enter two numbers: 2 4
Harmonic mean of 2 and 4 is 2.66667
Enter next set of numbers<q to quit>: 5 -5
bad hmean() arguments: x = - y not allowed!
Enter a new pair of numbers: 7 8
Harmonic mean of 7 and 8 is 7.46667
Enter next set of numbers<q to quit>: q
Bye!
Process returned 0 (0x0) execution time : 19.359 s
Press any key to continue.
可以看到,就算输入不符合要求,也不影响后面再继续使用这个函数,只是当前这组输入不会得到结果而已。
而使用abort函数的方法一旦遇到输入有问题,程序就退出了,需要重启才可以继续使用。
而用返回错误码的方式,需要把函数的参数或者接口的设计改一下,不是最简单,只实现功能的样子了。
异常的类型:把对象作为异常的类型,引发异常的函数传递出来一个对象
这么做有什么好处,为什么传对象,为什么把对象作为异常的类型?
- 对象可以携带信息,程序员可以根据对象携带的信息确定出引发异常的具体原因;并且catch块也可以根据这些信息来决定到底采取什么措施。
- 用不同的异常类型来区分不同的函数在不同的情况下引发的异常
示例:异常类,调和平均和几何平均
//exc_mean.h -- 给hmean(),gmean()函数专门写异常类
#ifndef _EXCMEAN_H_
#define _EXCMEAN_H_
#include <iostream>
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0):v1(a),v2(b){}
void mesg();
};
inline void bad_hmean::mesg()
{
std::cout << "hmean(" << v1 << ", " << v2
<< "): " << "invalid arguments: a = -b\n";
}
class bad_gmean
{
private:
double v1;
double v2;
public:
bad_gmean(double a = 0, double b = 0):v1(a), v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg()
{
std::cout << "Values used: " << v1 << ", "
<< v2 << std::endl;
return "gmean() arguments should be >= 0\n";
}
#endif
//error4.cpp -- using exception classes
#include <iostream>
#include <cmath>
#include "exc_mean.h"
double hmean(double a, double b);//求调和均值
double gmean(double a, double b);//几何均值
int main()
{
using std::cout;
double x, y, ans;
cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
try
{
ans = hmean(x, y);
cout << "Harmonic mean of " << x << " and "
<< y << " is " << ans << std::endl;
cout << "Geometric mean of " << x << " and "
<< y << " is " << gmean(x, y) << std::endl;
cout << "Enter next set of numbers<q to quit>: ";
}
catch (bad_hmean & bh)
{
bh.mesg();
cout << "Try again!\n";
continue;
}
catch (bad_gmean & bg)
{
cout << bg.mesg();
cout << "Sorry, you don't get to play any more.\n";
break;
}
}
cout << "Bye!\n";
return 0;
}
double hmean(double x, double y)
{
if (x == -y)
throw bad_hmean(x, y);//利用构造函数创建一个对象并用throw回传
return 2.0 * x * y / (x + y);
}
double gmean(double x, double y)
{
if (x < 0 || y < 0)
throw bad_gmean(x, y);
return std::sqrt(x * y);
}
这段程序的两个异常类给出了不太一样的处理办法,一个返回字符串消息对象,让catch块来打印,一个自己直接打印;一个在catch块用continue,可以继续执行,一个用break,不让继续执行。
Enter two numbers: 2 5
Harmonic mean of 2 and 5 is 2.85714
Geometric mean of 2 and 5 is 3.16228
Enter next set of numbers: 5 -5
hmean(5, -5): invalid arguments: a = -b
Try again!
Enter next set of numbers<q to quit>: -9 8
Harmonic mean of -9 and 8 is 144
Values used: -9, 8
gmean() arguments should be >= 0
Sorry, you don't get to play any more.
Bye!
其实异常类的类设计中,也可以把两个参数设置为public的,这样在catch块中用返回的对象去访问参数并输出,而不用必须在异常类的mesg方法中输出打印。
catch (bad_gmean & bg)
{
cout << bg.mesg();
cout << "Values used: " << bg.v1 << ", "
<< bg.v2 << std::endl;
cout << "Sorry, you don't get to play any more.\n";
break;
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0):v1(a), v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() arguments should be >= 0\n";
}
栈的解退 unwinding the stack
先说说函数调用中怎么利用栈这种数据结构来实现回退。
具体来说,栈中会存放
- 调用被调函数时主调函数中那句调用代码的地址(便于后面回退时返回)
- 传给被调函数的参数(自动变量)
- 进入到被调函数后,被调函数自己创建的自动变量
函数的调用可以嵌套很多层,程序只会创建一个栈,编译器会指定栈的大小,所有函数的以上信息会按照调用顺序push到栈中,返回的时候则pop栈顶,这就是为什么每个函数在结束执行时会释放它的自动变量,因为要弹出栈顶元素才能回退到之前的调用层呀。
如果函数中创建的自动变量是类的对象,那么释放它,即把他pop掉时,如果这个类有显式析构函数,则程序会自动调用其显式析构函数,如果没有自己写析构函数,是不会调用默认析构函数的,因为反正默认析构函数也啥都不做,和不调用根本没有区别。
return和throw的区别(回退层数不同;且异常中,编译器会创建临时拷贝)
回退层数
return只会在栈中回退一级,即回到调用自己的函数就好了,即只会找第一个返回地址,且只处理被调函数放在栈中的自动变量,包括对象。
如果是throw导致栈回退,那么程序会一直回退,直到找到第一个位于try块中的返回地址,且throw语句会处理try块到throw之间的整个函数调用序列放在栈中的自动变量,包括对象(释放其内存等)。
throw有可能回退很多层,因为它回退的终极目标是要找到包含会引发这个异常的try块。
- 如果try块调用的函数引发了这个异常,那么throw回退一层就找到了;
- 如果try调用的函数调用的函数引发了这个异常,那就要回退两层···
- 以此类推,总之就是要一直退,直到找到try块,好去匹配和执行try块后面的catch块的代码以处理这个异常。
- 如果退到底都没找到try块,程序就会调用abort函数。
这下区别讲的够清楚了。
相同点:
- 都是利用栈来实现回退
- 都会处理栈中存放的对象,即如果有则会调用其析构函数进行必要处理,如释放内存等。
异常中,编译器会创建临时拷贝
函数的返回机制中,如果返回参数是引用类型,则编译器直接返回引用,不会创建什么副本;
但是在引发异常中,就算catch参数类型是引用,编译器依然会创建一个副本,即传回的引用是指向副本的引用。
所以根本没有任何必要把catch参数设置为const引用!反正指向的都是副本,改了又咋地。
class Problem{···};
void super()
{
if (on_no)
{
throw Problem();//或者分为两步,先构建异常对象,再抛出Problem oops; throw oops;
}
}
try{
super();
}
catch (Problem & p){
···
}
比如这个例子中,p指向的是oops的副本,并不是oops本身。毕竟oops是自动对象变量,super函数执行结束就没了。
但是既然会创建副本,那可以直接传值啊,catch (Problem p)
,干嘛仍然要传引用呢。毕竟一般来说,让引用或者指针作为返回类型就是为了避免创建副本浪费时间,效率低下。
在异常处理中,仍然选择传引用,是因为引用还有另一个好处,在异常处理中大有用处。这个好处就是:基类的引用可以指向派生类对象。
如果有一组通过继承关联起来的异常类型,那么在catch语句中只需要写基类引用,就可以捕捉任何派生类的异常对象。但是如果catch参数写派生类引用,则就只可以捕捉这个派生类的对象了。
引发的异常会被第一个与之匹配的catch块捕捉。所以catch块的排列顺序和派生顺序相反,即孩子类在前,祖先类在后, 基类在最后面。比如:
class bad_1{···};
class bad_2 : public bad_1
{···};
class bad_3 : public bad_2
{···};
void duper()
{
if (oh_no)
throw bad_1();
if (rats)
throw bad_2();
if (drat)
throw bad_3();
}
try{
duper();
}
catch (bad_3 & be){
···
}
catch (bad_2 & be){
···
}
catch (bad_1 & be){
···
}
示例
这个示例专门建了一个简单的演示类demo,为了显示程序执行位置,便于从输出看到异常throw后回退的过程。
//exc_mean.h -- 给hmean(),gmean()函数专门写异常类
#ifndef _EXCMEAN_H_
#define _EXCMEAN_H_
#include <iostream>
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0):v1(a),v2(b){}
void mesg() const;
};
inline void bad_hmean::mesg() const
{
std::cout << "hmean(" << v1 << ", " << v2
<< "): " << "invalid arguments: a = -b\n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0):v1(a), v2(b){}
const char * mesg() const;
};
inline const char * bad_gmean::mesg() const
{
return "gmean() arguments should be >= 0\n";
}
#endif
//error5.cpp -- unwinding the stack
#include <iostream>
#include <cmath>
#include <string>
#include "exc_mean.h"
class demo
{
private:
std::string word;//包含
public:
demo(const std::string & str)
{
word = str;
std::cout << "demo " << word << " created\n";
}
~demo()
{
std::cout << "demo " << word << " destroyed!\n";
}
void show() const
{
std::cout << "demo " << word << " lives!\n";
}
};
double hmean(double a, double b);
double gmean(double a, double b);
double means(double a, double b);
int main()
{
using std::cin;
using std::cout;
using std::endl;
double x, y, z;
{
demo d1("found in block in main()");
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try{
z = means(x, y);
cout << "The mean mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Enter next pair: ";
}
catch (const bad_gmean & bg){
cout << bg.mesg();//bg是const对象,则mesg方法必须是const成员方法才行,否则报错,因为编译器担心const对象被修改
//const对象只能调用const方法
cout << "Values used: " << bg.v1 << ", "
<< bg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
}
catch (const bad_hmean & bh){
bh.mesg();
cout << "Try again.\n";
continue;
}
}
d1.show();
}
cout << "Bye!\n";
cin.get();
cin.get();
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
throw bad_hmean(a, b);
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b)
{
if (a<0 || b<0)
throw bad_gmean(a, b);
return std::sqrt(a * b);
}
double means(double a, double b)
{
demo d2("found in means()");
double hm, gm, am;//调和均值,几何均值,算术均值
am = (a + b) / 2.0;
try{
hm = hmean(a, b);
gm = gmean(a, b);
}
catch (const bad_hmean & bh){
bh.mesg();
std::cout << "Caught in means()\n";
throw;//重新抛出异常(先捕获,然后再次抛出,则会回退到main的try块)
//只使用throw,不指定异常,就会重新引发原来的异常
}
//未在means()中写和bad_gmean类对象匹配的异常处理程序(catch块)
d2.show();
return (am + hm + gm) / 3.0;
}
demo found in block in main() created
Enter two numbers: 2 5
demo found in means() created
demo found in means() lives!
demo found in means() destroyed!
The mean mean of 2 and 5 is 3.17314
Enter next pair: 2 -2
demo found in means() created
hmean(2, -2): invalid arguments: a = -b
Caught in means()
demo found in means() destroyed!
hmean(2, -2): invalid arguments: a = -b
Try again.
2 -4
demo found in means() created
demo found in means() destroyed!
gmean() arguments should be >= 0
Values used: 2, -4
Sorry, you don't get to play any more.
demo found in block in main() lives!
demo found in block in main() destroyed!
Bye!
这次在main函数return之前,放了两句代码,这样程序就不会退出,而会等我输入。之所以写两次,是因为担心换行符被第一句读完就还是退出了。
cin.get();
cin.get();
只写一句cin.get()或者一句都不写,就会退出程序:
总结
- break是跳出while哦,不是跳出catch块。continue,break的继续和跳出都是针对for和while块或者switch块的。即只管循环和选择模块,并不是对所有的块。
- main函数里专门写一个块,只是为了便于观察demo对象的析构函数被调用时候的输出信息,如果不用块包起来,则我们看不到
demo found in block in main() lives!
demo found in block in main() destroyed!
去掉main中的块后:
demo found in block in main() created
Enter two numbers: 4 -9
demo found in means() created
demo found in means() destroyed!
gmean() arguments should be >= 0
Values used: 4, -9
Sorry, you don't get to play any more.
Bye!
emmm,其实我不明白为啥不显示demo found in block in main() lives!
。。。。d1又没被析构,,,想了好久都没想通
- means函数捕捉了hmean函数引发的异常,但是通过下列输出可以看到,means中的
d2.show();
没被执行,说明means函数提前终止了(终止还是会调用d2的析构,因为要pop掉d2,所以d2的析构还是会被调用,会显示demo found in means() destroyed!
)。
其实这里终止是由means函数中对hmean函数引发异常的catch块中的throw语句导致的。
可以看到,catch块中可以再次throw自己正在处理的异常,还不需要写参数了。(有点像踢皮球,自己不管了,踢给更上层的try块去处理,这里就回退到了main的try块并被捕捉)
Enter next pair: 2 -2
demo found in means() created
hmean(2, -2): invalid arguments: a = -b
Caught in means()
demo found in means() destroyed!
hmean(2, -2): invalid arguments: a = -b
Try again.
- means函数没有捕捉gmean函数可能引发的错误,即虽然放在try块了,但是没有写参数类型为bad_gmean类型对象的catch块。所以当gmean函数引发异常时,程序会首先回退到means函数,但是找不到对应catch块,通过下列输出可以判断,means中的
d2.show();
没被执行,说明means函数提前终止了,回到了main函数。
这是因为程序在means函数中找不到catch块来捕捉gmean引发的异常,于是程序就会继续栈回退找上一个try块,并且同时不会再执行means函数的后续代码(提前终止)。这里是回到main中的try块并成功找到了匹配的catch块。
但是可以看到,回到main后,main程序是正常终止的,因为d1.show()打印了
2 -4
demo found in means() created
demo found in means() destroyed!
gmean() arguments should be >= 0
Values used: 2, -4
Sorry, you don't get to play any more.
demo found in block in main() lives!
demo found in block in main() destroyed!
Bye!
- 如果把gmean的catch块的break改为exit函数,则程序会立刻终止
catch (const bad_gmean & bg){
cout << bg.mesg();//bg是const对象,则mesg方法必须是const成员方法才行,否则报错,因为编译器担心const对象被修改
//const对象只能调用const方法
cout << "Values used: " << bg.v1 << ", "
<< bg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
exit(1);
}
demo found in block in main() created
Enter two numbers: 2 -7
demo found in means() created
demo found in means() destroyed!
gmean() arguments should be >= 0
Values used: 2, -7
Sorry, you don't get to play any more.
Process returned 1 (0x1) execution time : 23.395 s
Press any key to continue.
将看不到
demo found in block in main() destroyed!
Bye!
-
可见,这个示例的
d1.show();
和d2.show();
对于观察程序是否提前终止很有用,因为demo found in means() destroyed!
这种信息无法判断是不是提前终止。不管提前还是正常终止,只要终止,都会调用析构,显示这个destroyed信息。 -
如果catch的参数是const类型的对象,则这个对象在catch块中只可以调用该类的const成员函数。如果不是const成员函数,调用就会出错。因为编译器担心这些函数修改这个对象,为了安全考虑。就和之前经常说的非const转为const会报错是一样的理由。
我自己写代码,没怎么看书,于是不小心把catch参数写成了const对象,于是编译器对代码报错,我发现报错原因是因为bad_gmean类和bad_hmean类的mesg()方法不是const成员函数。如下
catch (const bad_hmean & bh){
bh.mesg();
cout << "Try again.\n";
continue;
}
报错:
解决办法有两种,一种是直接把catch块的参数改为非const类型;另一种是把bad_hmean和bad_gmean类的mesg方法改为const成员函数,如下(上面的示例代码采用了第二种办法):
const char * mesg() const;
inline const char * bad_gmean::mesg() const
{
return "gmean() arguments should be >= 0\n";
}
解决完了问题,再思考一下,其实,把catch的参数设为const类型完全没有必要,因为throw传回来的是异常对象的副本的引用!!!
况且一般情况下我们是不会修改它的,只是利用它携带的信息以分析异常产生的原因等,不需要修改这个对象,唯一可能修改它的情况就是不小心修改了,没注意犯错了,即出于正常思维和有用的角度的话,是根本不会修改传回的对象的。
但是设为const就可以多一重保护吧,如果代码非常强调安全性,也许还是有必要的。但我还是觉得没有任何必要,也没见过哪里代码写的const引用。