C/C++ 通常都是通过将信息放在栈中处理函数调用的。在程序调用一个函数时,同时会将函数的返回地址放到栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里继续执行。此外,程序还将被调函数的参数放到栈中,这些栈中的参数被视为自动变量。如果被调函数还调用了其它函数,那后者的信息也以同样的方式添加到栈中,依此类推。当函数结束时,程序流程将跳到该函数被调用时存储的返回地址处,同时栈顶的元素被释放。因此,函数都通常返回到调用它的函数,依此类推,同时每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用。(图-1)
现在假设函数出现了异常而终止(并不是通过 return 来正常返回)。这时候,程序也将释放栈中的内存,但不会在栈中的第一个返回地址哪里停止,而是继续释放栈,直到找到一个位于 try 块中的返回地址,如下图所示:
(图-2)
随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为栈解退。
引发(或称为抛出, throw)机制的一个非常重要的特征是,和函数返回一样,对于栈中的自动类对象,类的析构函数将被调用。然而,函数返回仅仅处理该函数放在栈中的对象;而 throw 语句则处理 try 块和 throw 之间整个函数调用序列放在栈中的对象(参考 图-2 的返回标示线)。如果没有栈解退这种特性,则引发异常后,对于中间函数放在栈中的自动类对象,其析构函数将不会被调用。
下面示例程序演示了栈解退。
exc_mean.h
[C++] 纯文本查看 复制代码 // exc_mean.h -- exception classes for hmean(), gmean()
#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
{
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";
}
error5.cpp
[C++] 纯文本查看 复制代码 //error5.cpp -- unwinding the stack
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#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";
}
};
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
double means(double a, double b);
int main()
{
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
{
demo d1("found in block in main()");
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try { // start of try block
z = means(x, y);
cout << "The mean mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Enter next pair: ";
} // end of try block
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg)
{
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", "
<< hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
}
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)
{
double am, hm, gm;
demo d2("found in means()");
am = (a + b) / 2.0; // arithmetic mean
try
{
hm = hmean(a, b);
gm = gmean(a, b);
}
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
std::cout << "Caught in means()\n";
throw; // rethrows the exception
}
d2.show();
return (am + hm + gm) / 3.0;
}
在上面程序中,main() 调用了 means() ,该函数用来计算算术平均数,调和平均数以及几何平均数,而 means() 又调用了 hmean() (计算调和平均数)和 gmean() (计算几何平均数) 。
在 main() 和 means() 中都创建了 demo 类型对象,该对象用来输出一些信息,指出什么时候构造函数和析构函数被调用了,这样容易从中了解发生异常时这些对象都如何被处理的。
在 main() 中的 try 块能够捕获 bad_hmean 和 bad_gmean 两种异常,而函数 means() 中的 try 块只能捕获 bad_hmean 异常。
在 means() 中的 catch 块里,单独使用了一个 throw 语句,它用来重新引发异常。
测试程序的输出:
程序说明:
在第一次的输入(6 和 12)中,首先在 main() 中创建了一个 demo 对象。接着调用 means() 函数,它又创建了另一个 demo 对象。means() 用 6 和 12 来调用了 hmean() 和 gmean(),它们将结果返回给 means() ,后者计算一个结果((am + hm + gm) / 3.0;)并将其返回。在返回借过钱,means() 调用了 d2.show(),在返回结果后,means() 也就执行完毕了,此时自动为 d2 调用析构函数。
第第二次的测试输入(6 和 -6) 中,,means() 创建了一个新的 demo 对象,并将值传递给 hmean() ,此时会引发 bad_hmean 异常,该异常被 means() 中的 catch 块捕获,从输出:
hmean(6, -6) : invalid arguments: a = -b
Caught in menas()
可以看到这一点。该 catch 块中的 throw 语句将导致 means() 终止执行,这个重新引发的异常会传递给 main() 函数。语句 d2.show() 没有被执行,这表明了 means() 被提前终止了。但需要注意的是,还是为 d2 调用了析构函数,从输出:
demo found in means() destroyed
可以看到这一点,这也是非常重要的一点:程序进行栈解退以回到能够捕获异常的地方时,将释放栈中的自动存储变量。如果变量是类对象,将为该对象调用析构函数。
另外,通过 throw 重新引发的异常在传递给 main() 函数后,main() 中有合适的 catch 块捕获它并对其进行了处理,从输出结果也能看到这一点:
hmean(6, -6): invalid arguments: a = -b
Try again.
在第三次循环的输入(6 和 -8)中。同样,means() 创建了一个新的 demo 对象,然后将参数传递给 hmean(),该函数处理这两个参数时不会有什么问题,但是传递给 gmean() 时就会引发异常了。由于 means() 不能捕获 bad_gmean 异常,因此异常被传递给 main(),同时不再执行 means() 中的其它代码。同样,在程序进行栈解退时,将释放局部的动态变量,因此 d2 调用了析构函数。
最后,在 main() 中的 bad_gmean 异常处理捕获了该异常,循环结束。
|