异常
为什么要使用exceptions?
exceptions无法被忽略,如果一个函数利用设定状态变量或是利用返回错误码的方式发出异常信号,无法保证此函数调用者会检查错误码,于是程序
就会持续下去,从而远离错误点,但是有了exceptions,如果没有被捕获到,则程序会立刻终止。
条款9:利用destructors避免内存泄漏
即避免使用普通指针而去转向智能指针,或者说将资源封装到对象内,通常便可在异常出现时避免泄漏资源。
如下示例:GUI应用软件中的某个函数,必须产生一个窗口显示某些信息:
void displayInfo(const Infor& info)
{
//WINDOW_HANDLE w(createWindow()); //创建
//... display info //展示信息
//destroyWindow(w); //释放资源
}
//如果在创建与释放中间发生异常,则w持有的窗口将会遗失,其它动态分配的资源也会遗失
//采用中间类,在其构造和析构函数中获取和释放资源
class WindowHandle
{
public:
WindowHandle(WINDOW_HANDLE handle):w(handle){}
~WindowHandle(){destroyWindow(w);}
operator WINDOW_HANDLE(){return w;} //隐式转换符 WindowHandle -> WINDOW_HANDLE
private:
WINDOW_HANDLE w;
WindowHandle(const WindowHandle&) = delete;
WindowHandle& operator=(const WindowHandle&) = delete;
};
//重写display
void dislayInfo2(const Infor& info)
{
WindowHandle(createWindow()); //创建
... display info //展示信息
}
条款10:在constructors内阻止资源泄漏
c++只会析构已经构造完成的对象,对象也只有在constructor执行完毕时才算构造完成,因此如果在constructor函数中发生异常的话此对象,因此
为解决在构造函数中处理异常,可以使用
- member initialization lists 结合私有成员函数
class Image;
class BookEntry
{
public:
//初始化列表调用私有成员函数来解决构造函数中抛出的异常
BookEntry(const string& filename):imageFile(initImage(filename)){}
~BookEntry() {delete imageFile;}
private:
//私有化成员函数,捕获异常
Image* initImage(const string& fileName)
{
try{
if(fileNmae != "")
return new Image(fileName);
}
catch(...)
{
delete imageFile;
throw;
}
}
private:
Image* imageFile;
};
- 智能指针初始化动态内存
class Image;
class BookEntry
{
public:
//使用智能指针来解决构造函数中抛出的异常,好处是不用担心释放 imageFile内存问题
BookEntry(const string& filename):imageFile(new Image(filename)){}
~BookEntry() {delete imageFile;}
private:
//私有化成员函数,捕获异常
Image* initImage(const string& fileName)
{
try{
if(fileNmae != "")
return new Image(fileName);
}
catch(...)
{
delete imageFile;
throw;
}
}
private:
std::unique_ptr<Image> imageFile;
};
条款11:禁止异常流出destructors之外
class Session
{
public:
void destoryFun();
~Session()
{
try
{
destroyFun();
}
catch(...){} //捕获异常看起来什么都没做,但是阻止了异常外流
}
};
理由:
- 它可以避免terminate函数在异常传播过程中栈展开机制被调用;
- 可以协助确保destructor完成其应该完成的所有事
条款12:了解 "抛出异常"、"传递参数","调用函数" 的差异
class Widget;
void f1(Widget w);
void f2(Widget& w);
catch (Wdiget w)
catch (Wdiget& w)
catch (const Wdiget& w)
catch (Wdiget* w)
catch (const Wdiget* w)
- 函数参数和异常传递方式都有三种:按值、按引用、按指针
- 调用函数时,控制权最终会回到调用端;抛出异常时控制权不会回到抛出端
- 一个对象被抛出异常时总是会发生复制,因为这样会避免对象的瓦解风险,不论捕获的异常是by value\by reference
- 被抛出的对象被允许的转换动作,比传参到函数少,如 by reference方式捕获 = by reference-to-const
- 异常的调用者或者抛出者和被调用者或捕获者之间存在的类型吻合规则,类型必须的绝对匹配,且第一个匹配成功者便执行
条款13:以 by reference方式捕获异常
原因:
- 避免对象删除问题
- 避开对象的切割问题
- 保留捕获标准exceptions的能力 【虚函数】
- 约束了对象被复制的次数
class exception
{
public:
virtual const char* what() throw();
};
class runtime_error:public exception
{
virtual const char* what() throw()
{
...
}
};
//用户自定义异常处理
class Validation_error
{
virtual const char* what() throw()
{
...
}
};
//throw Validation_error()
void doSomeThing()
{
try{
throw Validation_error();
}
catch(excepthon& e)
{
e.what(); //调用Validation_error类的实现函数
}
}