void processAdoptions(istream& dataSource)
{
while (dataSource) {
ALA *pa = readALA(dataSource);
try {
pa->processAdoption();
}
catch (...) { // 捕获所有异常
delete pa; // 避免内存泄漏
// 当异常抛出时
throw; // 传送异常给调用者
}
delete pa; // 避免资源泄漏
} // 当没有异常抛出时
}
void processAdoptions(istream& dataSource)
{
while (dataSource) {
auto_ptr<ALA> pa(readALA(dataSource));
pa->processAdoption();
}
}
void testBookEntryClass()
{
BookEntry *pb = 0;
try {
pb = new BookEntry("Addison-Wesley Publishing Company",
"One Jacob Way, Reading, MA 01867");//构造对象
...
}
catch (...) { // 捕获所有异常
delete pb; // 删除pb,当抛出异常时,new操作并未成功,赋值失败,pb是null
//因此delete pb并未解决资源泄漏
throw; // 传递异常给调用者
}
delete pb; // 正常删除pb
}
BookEntry::BookEntry(const string& name,
const string& address,
const string& imageFileName,
const string& audioClipFileName)
: theName(name), theAddress(address),
theImage(0), theAudioClip(0)//堆上资源
{
try { // 这try block是新加入的
if (imageFileName != "") {
theImage = new Image(imageFileName);
}
if (audioClipFileName != "") {
theAudioClip = new AudioClip(audioClipFileName);//当它发生异常时,theImage也能被清理
}
}
catch (...) { // 捕获所有异常
delete theImage; // 完成必要的清除代码
delete theAudioClip;
throw; // 继续传递异常
}
}
如果theImage和theAudioClip是常量指针,只能在构造函数中的初始化列表里面初始化(只能为表达式),无法在初始化列表里面使用try…catch…语句,导致无法捕获异常。可以用一个辅助函数初始化这些成员变量,在该函数中使用try…catch…语句捕获异常,将函数调用写在初始化列表中。缺点在于构造函数完成的动作分散在其他函数之中,后期维护困难。
class BookEntry {
...
Image * initImage(const string& imageFileName);
AudioClip * initAudioClip(const string&
audioClipFileName);
};
BookEntry::BookEntry(const string& name,
const string& address,
const string& imageFileName,
const string& audioClipFileName)
: theName(name), theAddress(address),
theImage(initImage(imageFileName)),
theAudioClip(initAudioClip(audioClipFileName))
{}
// theImage 被首先初始化,所以即使这个初始化失败也
// 不用担心资源泄漏,这个函数不用进行异常处理。
Image * BookEntry::initImage(const string& imageFileName)
{
if (imageFileName != "") return new Image(imageFileName);
else return 0;
}
// theAudioClip被第二个初始化, 所以如果在theAudioClip
// 初始化过程中抛出异常,它必须确保theImage的资源被释放。
// 因此这个函数使用try...catch 。
AudioClip * BookEntry::initAudioClip(const string&
audioClipFileName)
{
try {
if (audioClipFileName != "") {
return new AudioClip(audioClipFileName);
}
else return 0;
}
catch (...) {
delete theImage;
throw;
}
}
class BookEntry {
public:
... // 同上
private:
...
const auto_ptr<Image> theImage; // 它们现在是
const auto_ptr<AudioClip> theAudioClip; // auto_ptr对象
};
//这样做使得BookEntry的构造函数即使在存在异常的情况下也能做到不泄漏资源
//而且让我们能够使用成员初始化列表来初始化theImage 和 theAudioClip,如下所示:
BookEntry::BookEntry(const string& name,
const string& address,
const string& imageFileName,
const string& audioClipFileName)
: theName(name), theAddress(address),
theImage(imageFileName != ""
? new Image(imageFileName)
: 0),
theAudioClip(audioClipFileName != ""
? new AudioClip(audioClipFileName)
: 0)
{}
Session::~Session()
{
try {
logDestruction(this);
}
catch (...) { }
}
抛出异常、传递参数、调用虚函数之间的区别主要有三点:
(1)异常对象在传递时(从throw传递到catch)总被进行拷贝(包括by-value、by-reference,by-pointer),生成一个临时对象;当通过传值(by-value)方式捕获时,异常对象被拷贝了两次(第一次是异常传递时生成的一个临时对象,第二次是临时对象给catch的形参赋值)。然而对象做为参数传递给函数时不一定需要被拷贝。
(2)对象做为异常被抛出与做为参数传递给函数相比,前者类型转换比后者要少(前者只有两种转换形式)。
(3)catch子句进行异常类型匹配的顺序是它们在源代码中出现的顺序第一个类型匹配成功的catch将被用来执行。当一个对象调用一个虚拟函数时,被选择的函数位于与对象类型匹配最佳的类里,即使该类不是在源代码的最前头。
千万不要抛出一个指向局部变量的指针。
异常对象抛出时,一般是严格匹配(int只能匹配int,不能匹配double等等隐式转换),只能有两种类型转换:
(1)catch声明接收基类对象,异常可以抛出对应的基类类型和派生类类型对象;
(2)catch声明接收const void *指针,异常可以抛出所有类型的指针;
不要把处理基类异常的catch子句放在处理派生类异常的catch子句的前面,因为异常catch匹配是顺序匹配(最先匹配的catch),不是最佳匹配。
函数调用过程中不允许将一个临时对象传递给一个non-const-reference参数,因为函数参数要求传入引用说明想修改引用的变量,结果变量是临时对象,即使修改了也是做无用功,所以编译器不允许;但是异常里面可以,因为异常里面参数传递都是副本,传引用也不是为引用异常抛出前的原始对象,而是相较于by-value减少一次副本拷贝。
class exception { // 如上,这是
public: // 一个标准异常类
virtual const char * what() throw();
// 返回异常的简短描述.
... // (在函数声明的结尾处
// 的"throw()",
}; //有关它的信息
// 参见条款14)
class runtime_error: //也来自标准C++异常类
public exception { ... };
class Validation_error: // 客户自己加入个类
public runtime_error {
public:
virtual const char * what() throw();
// 重新定义在异常类中
... //虚拟函数
}; //
void someFunction() // 抛出一个 validation
{ // 异常
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction(); // 抛出 validation
} //异常
catch (exception ex) { //捕获所有标准异常类
// 或它的派生类
cerr << ex.what(); // 调用 exception::what(),
... // 而不是Validation_error::what()
}
}
void someFunction() //这个函数没有改变
{
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction(); // 没有改变
}
catch (exception& ex) { // 这里,我们通过引用捕获异常
// 以替代原来的通过值捕获
cerr << ex.what(); // 现在调用的是
// Validation_error::what(),
... // 而不是 exception::what()
}
}
// 这个模板包含的异常规格表示模板生成的函数不能抛出异常。
//但是事实可能不会这样,因为opertor&能被一些类型对象重载。
//如果被重载的话,当调用从operator==函数内部调用opertor&时,
//opertor&可能会抛出一个异常,这样就违反了我们的异常规格,使得程序控制跳转到unexpected。
template<class T>
bool operator==(const T& lhs, const T& rhs) throw()
{
return &lhs == &rhs;
}
class UnexpectedException {}; // 所有的unexpected异常对象被
//替换为这种类型对象
void convertUnexpected() // 如果一个unexpected异常被
{ // 抛出,这个函数被调用
throw UnexpectedException();
}
//通过用convertUnexpected函数替换缺省的unexpected函数,来使上述代码开始运行。:
set_unexpected(convertUnexpected);
另一种把unexpected异常转变成已知类型的方法是替换unexpected函数,让其重新抛出当前异常,这样异常将被替换为bad_exception。
void convertUnexpected() // 如果一个unexpected异常被
{ //抛出,这个函数被调用
throw; // 它只是重新抛出当前
} // 异常
set_unexpected(convertUnexpected); // 安装 convertUnexpected
// 做为unexpected的替代品