class Animal {
public:
virtual void processAdoption() = 0;
}
class Cat : public Animal {
public:
virtual void processAdoption();
}
class Dog : public Animal {
public:
virtual void processAdoption();
}
void batchAdoption(istream& dataSource) {
while (dataSource) {
Animal *animal = readAnimal(dataSource); // 可能抛出异常
animal->processAdoption(); // 可能抛出异常
delete animal;
}
}
// 在上面的batchAdoption()方法中
// readAnimal()和 processAdoption() 都可能抛出异常
// 程序中断,从而导致delete animal无法执行,内存泄漏发生。
void batchAdoption(istream& dataSource) {
while (dataSource) {
Animal *animal = readAnimal(dataSource); // 不能放入try中,否则animal对外部不可见
try {
animal->processAdoption();
} catch (...) {
delete animal; // 代码冗余
throw;
}
delete animal; // 代码冗余
}
}
void batchAdoption(istream& dataSource) {
while (dataSource) {
auto_ptr animal(readAnimal(dataSource));
animal->processAdoption();
// 无需调用语句delete animal,出了作用域即调用析构函数
}
}
new A,再 new B,然而在 new B 的过程中出现异常,此时 new A 指向的内存就会出现内存泄漏。BookEntry::BookEntry(const string &name, const string& address,
const string& image, const string& audio)
: m_name(name), // name 是非指针变量
m_address(address), // address 是非指针变量
m_image(createImage(image)), // image 和 audio 均是常量(const)指针
m_audio(createAudio(audio)) {
}
Image* createImage(const std::string& image) { // image 在 audio 面前初始化,所以不需要捕捉异常
if(image != "") return new Image(image);
else return 0;
}
Audio* createAudio(const std::string& audio) { // audio 第二个初始化,所以捕捉异常到异常后需要释放 image 对象
try {
if(audio != "") return new Audio(audio);
else return 0;
} catch () {
delete m_image; // 捕捉到异常需要释放已经初始化的 image 对象
throw;
}
}
// 可以看到针对m_image变量定义了构建函数
// 在heap中创建变量的工作放到这个构建函数中,并返回创建好的指针
image 指针和 audio 指针改为被智能指针包裹即可),利用作用域和生命周期来控制具体的行为。~Destructor() {
try {
doSomething();
} catch (...) {
// doNothing, avoid more exception
}
}
// 示例1:抛出的异常为局部变量
void passThrowWidget() {
Widget widget;
doSomething(widget);
// 抛出的对象是widget的一个副本
// 当前作用域的widget在离开本函数时已经被销毁
throw widget;
}
// 示例2:抛出的异常为静态局部变量
void passThrowWidget() {
static Widget widget;
doSomething(widget);
// 尽管本函数内的widget不会被销毁,但是抛出的widget依然是一个副本
throw widget;
}
// 无论原对象以什么形式定义,抛出的对象总是一个副本。
// 这样做保证了,catch捕获的对象总能存在,
// 否则可能导致捕获的异常对象已经被销毁。
class Widget; // 基类
class ChildWidget : public Widget { // 派生类
}
void passThrowWidget() {
ChildWidget child; // ChildWidget 是 child 的类型
Widget &widget = child; // Widget 是 widget 的静态类型
throw widget; // 调用 Widget 的复制构造函数进行复制,而不是 ChildWidget
}
catch (Widget& w) // 方案一:不复制异常,而是直接抛出当前的异常
{
// ...
throw; // 重新抛出当前的异常,不管 w 的动态类型是什么,最后都可以得到保证
}
catch (Widget& w) // 方案二:复制后抛出
{
// ...
throw w; // 抛出当前的异常的副本,相当于新的异常,且副本只保留了原对象静态类型
}
// 方案二带来的问题是:
// 复制操作带来的开销
// 复制行为是基于静态类型的拷贝,因此传递抛出的对象可能不是原来想要传递的对象
catch (Widget w)【by value 方式捕捉】catch (Widget &w)【by reference 方式捕捉】catch (const Widget &w)【by reference-to-const 方式捕捉】int 转为 double;而异常捕获的参数是不允许前述的基本隐式转换,即对 int 异常的抛出不会被捕获 double 异常的 catch 语句捕获到。catch (const void*) 可以捕捉任意指针类型的异常】try{}
catch (base& ex){
// ...
}
catch (derived& ex){ // 这个语句永远不会被执行,因为所有针对继承类的异常都被前面的语句捕获了
// ...
} // 要想该语句被执行,只能将该语句移到 catch(base& ex) 的前面去
void func() {
Widget error; // 形式1
static Widget errorStatic; // 形式2
Widget *errorHeap = new Widget; // 形式3
throw &error; // 形式1
throw &errorStatic; // 形式2
throw errorHeap; // 形式3
}
catch (Widget *widget) {
// ...
}
// 问题一的体现:
void func() {
Widget widget;
throw widget; // 第一次复制
}
catch (Widget widget) { // 第二次复制
}
// 问题二的体现:
class exception {
public:
virtual const char *what() throw(); // ”throw()“关键字声明该函数不会抛出任何异常
}
class DerivedException : public exception {
public:
virtual const char *what() throw(); // 虚函数实现多态
}
void func() {
DerivedException widget;
throw widget; // 的确是抛出了派生类的异常,但是捕获函数中会将其切割为基类,随后就调用了基类的 what 函数
}
catch (exception widget) { // 捕捉继承体系里的所有异常
widget.what(); // 调用的是exception::what(),这种情况叫做slicing(切割),即子类信息被切割掉,只留下基类的信息
}
void func() {
DerivedException widget;
throw widget;
}
catch (exception& widget) {
widget.what(); // 调用的是DerivedException的what,实现了多态
}
throw(),则表示该函数不抛出任何异常。void f2() throw(int); 表示 f2 只抛出类型为 int 的异常。unexpected() -> terminate() -> abort(),因此程序如果违反异常生命,缺省结果就是程序被中止。template<class T>
bool operator==(const T& left, const T& right) throw() { // 这样是一种不好的做法
return &left == &right;
}
// 我们无法确定,取地址操作符“&”是否已经被重载,且可能抛出异常。
// 此种情况的实质是,我们无法确定,所有类对象的同名函数都不会抛出异常。
throw() 进行修饰:如果函数 A 内调用了函数 B,而函数 B 无 exception specification,那么 A 函数本身也不要设定 exception specification。【内部允许产生所有异常,外部自然也不要加以限制】
typedef void (*CallbackPtr)();
class Callback {
public:
Callback(CallbackPtr func) : m_func(func) {}
void makeCallBack() throw() { // 这里的异常声明很容易带来问题
m_func(); // 可能抛出异常
}
private:
CallbackPtr m_func;
}
// 如代码所示,如果注册的“回调函数”没有throw修饰,
// 而调用“回调函数”的外层函数却有throw修饰,
// “回调函数”抛出异常就会引起程序终止。
set_unexpected(),可以向该函数中传递我们自定义的函数,来替换默认的 unexpected()。