条款9 使用析构函数防止内存泄漏

条款10 在构造函数中防止内存泄漏

条款11 禁止异常信息传递到析构函数外

条款12 理解"抛出一个异常''与"传递一个参数"或调用一个函数的差别

条款13  以by reference的方式捕获异常

条款14 明智的运用 exception specifications

条款15 了解异常处理的成本


条款9 使用析构函数防止内存泄漏

1.

 void processAdoptions(istream& dataSource)
{
while (dataSource) // 还有数据时,继续循环
{
ALA *pa = readALA(dataSource); //得到下一个动物
pa->processAdoption(); //处理收容动物
delete pa; //删除 readALA 返回的对象
}
}

如果在pa->processAdoption()时出现异常,则该方法后的方法将不在调用.也就是说pa指针不会被删除会造成内存泄漏问题

方法一:为了防止以上现象的发生我们可以使用try throw方法,但是这样完全破坏了你的代码,而且如果程序正常运行的话try和throw将无意义

 void processAdoptions(istream& dataSource)
{
while (dataSource)
{
ALA *pa = readALA(dataSource);
try
{
pa->processAdoption();
}
catch (...)// 捕获所有异常
{
delete pa; // 避免内存泄漏
//当异常抛出时
throw; // 传送异常给调用者
}
delete pa; // 避免资源泄漏
} // 当没有异常抛出时
}

方法二:auto_ptr类 (auto_ptr对象代替指针你将不在为堆对象不能删除而担心,因为auto_ptr的析构函数使用的是但对象形式的delete,所以auto_ptr不能用于指向对象数组的指针,但是可以用vector)

 void processAdoptions(istream& dataSource)
{
  while (dataSource) {
  auto_ptr<ALA> pa(readALA(dataSource));
  pa->processAdoption();
  }
}

内存应该被封装在一个对象里,遵循这个规则,你通常就能避免在异常环境中发生内存泄漏


条款10 在构造函数中防止内存泄漏

1.如果构造函数中出现异常(如内存分配不够),那么它的析构函数将不会调用,C++的析构函数只会被调用在构造完整的对象.指示构造函数进行到哪种程度,无疑会降低C++的效率,所以C++并没有提供指示构造程度的函数

那么我们将如何防止这些,我忙呢创建一个通讯录类

代码1

 class Image { // 用于图像数据
public:
  Image(const string& imageDataFileName);
...
};
class AudioClip { // 用于声音数据
public:
  AudioClip(const string& audioDataFileName);
...
};
class PhoneNumber { ... }; // 用于存储电话号码
class BookEntry { // 通讯录中的条目
public:
  BookEntry(const string& name,
        const string& address = "",
        const string& imageFileName = "",
        const string& audioClipFileName = "");
  ~BookEntry();
// 通过这个函数加入电话号码
void addPhoneNumber(const PhoneNumber& number);
...
private:
  string theName; // 人的姓名
  string theAddress; // 他们的地址
  list<PhoneNumber> thePhones; // 他的电话号码
  Image *theImage; // 他们的图像
  AudioClip *theAudioClip; // 他们的一段声音片段
};

编写通讯录时,如果new的过程中发生异常那么Image和Audio由于构造不完整,将不会调用析构函数,导致内存泄漏

代码2

 //错误实例
BookEntry::BookEntry(const string& name,const string& address,const string& imageFileName,const string& audioClipFileName)
: theName(name), theAddress(address),
theImage(), theAudioClip()
{
if (imageFileName != "") {
  theImage = new Image(imageFileName);
}
if (audioClipFileName != "") {
  theAudioClip = new AudioClip(audioClipFileName);
}
}
BookEntry::~BookEntry()
{
  delete theImage;
  delete theAudioClip;
}

方法一:try构造函数,出现异常时 image和audio是对象会自动析构

代码3

BookEntry::BookEntry(const string& name,const string& address,const string& imageFileName,const string& audioClipFileName)
: theName(name), theAddress(address),
theImage(), theAudioClip()//member initialization lists
{
  try { // 这 try block 是新加入的
    if (imageFileName != "") {
      theImage = new Image(imageFileName);
    }
    if (audioClipFileName != "") {
      theAudioClip = new AudioClip(audioClipFileName);
    }
  }
  catch (...) { // 捕获所有异常
    delete theImage; // 完成必要的清除代码
    delete theAudioClip;
    throw; // 继续传递异常
  }
}

member initialization lists 直接受experssions(表达式)不接受Statement(语句)

方法三:使用auto_ptr(参考条款9,将Image和Audio生命成资源)(推荐使用该方法)

代码4

class BookEntry {
public:
... // 同代码1
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): ),
theAudioClip(audioClipFileName != ""? new AudioClip(audioClipFileName): )
{}
//析构函数可以写成
BookEntry::~BookEntry()
{}

如果你用对应的 auto_ptr 对象替代指针成员变量,就可以防止构造函数在存在异常时放生资源泄漏,你一不用手工在析构函数中释放资源,并且你还能像以前使用非const指针一样使用const给其赋值


条款11 禁止异常信息传递到析构函数外

一个对象有两种情况下回调用析构函数

a.正常删除该对象.

b.异常传递的堆栈展开(stack-unwinding)由于系统异常导致对象被删除

如果在一个异常被激活的同时,析构函数也抛出异常,并导致程序控制权转移到析构函数外,C++将调用 terminate 函数(terminate函数如其名,立即终止掉你的程序,甚至连局部对象都没有释放)

禁止禁止异常信息传递到析构函数外有两个原因

1.程序的异常终止

下面 如果 logDestruction 抛出一个异常,会发生什么事呢?

代码1

 class Session {
public:
Session();
~Session();
...
private:
static void logCreation(Session *objAddr);//记录对象创建
static void logDestruction(Session *objAddr);//记录对象释放
}
//析构函数
Session::~Session()
{
logDestruction(this);
}

异常没有被 Session 的析构函数捕获住,所以它被传递到析构函数的调用者那里。但是如果析构函数本身的调用就是源自于某些其它异常的抛出,那么 terminate 函数将被自动调用,彻底终止你的程序.

解决方案

代码2

 Session::~Session()
{
try {
logDestruction(this);
}
catch (...) { }//它并没有什么都没有,而是不抛出任何异常
}

它阻止了任何从logDestruction 抛出的异常被传递到 session 析构函数的外面。我们现在能高枕无忧了,无论 session 对象是不是在堆栈退栈(stack unwinding)中被释放,terminate 函数都不会被调用.

2.如果一个异常被析构函数抛出而没有在函数内部捕获住,那么析构函数就不会完全运行(它会停在抛出异常的那个地方上)。如果析构函数不完全运行,它就无法完成希望它做的所有事情


条款12 理解"抛出一个异常''与"传递一个参数"或调用一个函数的差别

1.相似:

函数return值和try throw exception,函数接受参数与catch捕获他们的声明相似,函数参数与exception传递方式都有三种:by value,by reference ,by pointer(本质上也是by value,但是thro pointer是不发生对象复制的,会发生指针复制)

2.差别:

1)尽管函数调用与异常抛出非常相似,但是发生的事情可能完全不同,调用函数时控制权最终会回到调用端(除非函数失败),但是抛出一个异常控制权不会在回到抛出端.

2)如果一个函数的调用参数为reference 那么实参不会被复制,但是catch无论是by value 还是by value 都会至少发生一次复制

有这样一个函数,参数类型是 Widget,并抛出一个 Widget 类型的异常:一个函数,从流中读值到 Widget 中

istream operator>>(istream& s, Widget& w);
void passAndThrowWidget()
{
Widget localWidget;
cin >> localWidget; //传递 localWidget 到 operator>>
throw localWidget; // 抛出 localWidget 异常
}

localWidget交到operator>>时并没有发生复制,而是将参数w呗绑定在Widget上这和throw的情况完全不一样,无论被捕捉的exception是以什么形式(value,refernce)都会发生localWidget的复制,抛出后栈消亡Widget会调用析构函数(C++中一个对象被抛出异常时,总会发生复制,即使用static声明,抛出的异常还是一个被复制的副本)

3)函数的调用参数可以动态识别,但是抛出的对象不会动态识别

当对象被复制时调用的copy constructor,这个函数相应于对象的静态类型(而非动态类型)

class Widget { ... };
class SpecialWidget: public Widget { ... };
void passAndThrowWidget()
{
  SpecialWidget localSpecialWidget;
  ...
  Widget& rw = localSpecialWidget; // rw 引用 SpecialWidget
  throw rw; //它抛出一个类型为 Widget// 的异常
}

4)函数调用可以局部变量传递指针,但是抛出exception不可抛出局部变量指针,因为抛出后该函数的栈空间会消亡,局部对象也会被销毁,因此catch子句会获得一个纸箱"已被销毁的对象,这是"义务性的copy原则",设计上要避免

5)catch语句在捕获异常时不会发生隐式类类型转换,但是支持两种转换"继承架构中的exception""和"有型指针转为无型指针"

void f(int value)
{
try
{
if (someFunction())
    { // 如果 someFunction()返回
    throw value; //真,抛出一个整形值
    ...
   }
  }
  catch (double d) { // 只处理 double 类型的异常不会接收int
  ...
  }
...
}

继承架构中的exception: 这是一个针对exception的class

一个针对runtime_errors而编写的catch子句,可以捕获类型为range_error和overflow]_error的exception

有型指针转为无型指针:catch (const *void) //可以捕获任意类型的指针

6)传递参数和传播异常的最后一个不同点,catch子句总是会依出现的顺序进行匹配尝试

try{
...
}
catch(logic_error &ex){//此语句块将捕获所有的logic_error 异常甚至包括其子类异常
...
}
catch(invalid_argument& ex){//不会捕获到已被上面捕获
..
}

3.异常是其它对象的拷贝,这个事实影响到你如何在 catch 块中再抛出一个异常

catch (Widget& w) // 捕获 Widget 异常
{
... // 处理异常,重新抛出异常,让它继续传递,不会发生拷贝
throw;
}
catch (Widget& w) // 捕获 Widget 异常
{
... // 处理异常,传递被捕获异常的,发生拷贝
throw w;
}

条款13  以by reference的方式捕获异常

1.正确的使用抛出指针类型异常相似于下面代码

class exception { ... }; // 来自标准 C++库(STL)中的异常类层次(参见条款 12)
void someFunction()
{
static exception ex; // 异常对象
...
throw &ex; // 抛出一个指针,指向 ex
...
}
void doSomething()
{
try {
someFunction(); // 抛出一个 exception*
}
catch (exception *ex) { // 捕获 exception*;
... // 没有对象被拷贝
}
}

然而有很多人容易抛出一个局部变量的指针,如果抛出一个new exception 会涉及到一些删除问题和创建异常问题

2.有些时候抛出异常必须使用 by value 和 by reference:

如exception---bad_alloc(创建时内存不足异常),bad_cast(转换异常),bad_typeid(档dynamic_cast被施加一个null指针身上时),bad_exception(未预期的异常状态)这些都是对象不能使用抛出异常指针

3..使用by value的方式抛出异常时,容易发生切割(slicing)即不会动态识别.

4.虽然抛出reference会造成一些拷贝,但是优点多于缺点,推荐使用抛出引用


条款14 明智的运用 exception specifications

exception specifications即抛出指定的异常,如果一个函数指定了exception specifications但是抛出位列其中的异常那么特殊函数unexpected将会被调用(unexpected默认调用的是terminate会使程序abort)

考虑以下富人f1函数声明,它没有声明exception specificcation所以此函数可以抛出所有异常

extern void f1()

声明exception specifications的函数f2

void f2() throw(int) 直抛出int型的异常

在C++中f2调用f1 合法

void f2() throw(int)
{
...
f1(); // 即使 f1 可能抛出不是 int 类型的异常,这也是合法的。
...
}

这种弹性是必要的,即使f1抛出的异常导致程序异常终止所以我们要将这种不一致性降到最低

1.避免程序unexpected方法1:不应该将templates和exception specification混合使用

下面的template好像绝对不会抛出一个异常

template<class T>
bool operator==(const T& lhs, const T& rhs) throw()//不抛出异常
{
return &lhs == &rhs;
}

但是如果某些类重载了operator&,那么该template有可能抛出一个operator&的异常导致程序异常终止

2.避免程序unexpected方法2:如果函数A调用了函数B 而B函数没有声明exception specifictions,那么A也不要声明

还有一点回调函数callback

 // 一个 window 系统回调函数指针
//当一个 window 系统事件发生时
typedef void (*CallBackPtr)(int eventXLocation,
int eventYLocation,
void *dataToPassBack);
//window 系统类,含有回调函数指针,
//该回调函数能被 window 系统客户注册
class CallBack {
public:
CallBack(CallBackPtr fPtr, void *dataToPassBack)
: func(fPtr), data(dataToPassBack) {}
void makeCallBack(int eventXLocation,
int eventYLocation) const throw();
private:
CallBackPtr func; // function to call when callback is made
void *data; // data to pass to callback
}; // function 为了实现回调函数,我们调用注册函数,事件的作标与注册数据做为函数参数。
void CallBack::makeCallBack(int eventXLocation,
int eventYLocation) const throw()
{
func(eventXLocation, eventYLocation, data);
}

这里在 makeCallBack 内调用 func,要冒违反异常规格的风险,因为无法知道 func 会抛出什么类型的异常

为了避免上述问题我们可以使用typedef

 typedef void (*CallBackPtr)(int eventXLocation,
int eventYLocation,
void *dataToPassBack) throw();//这样定义 typedef 后,如果注册一个可能会抛出异常的 callback 函数将是非法的 // 一个没有异常规格的回调函数
void callBackFcn1(int eventXLocation, int eventYLocation,
void *dataToPassBack);
void *callBackData;
...
CallBack c1(callBackFcn1, callBackData);//错误!callBackFcn1 可能 抛出异常 //带有异常规格的回调函数
void callBackFcn2(int eventXLocation,
int eventYLocation,
void *dataToPassBack) throw();
CallBack c2(callBackFcn2, callBackData);// 正确,callBackFcn2 没有异常规格

2.避免程序unexpected方法3:处理"系统可能抛出的异常"

1)阻止非预期的exception

例如你希望所有的 unexpected 异常都被替换UnexpectedException 对象。你能这样编写代码

 class UnexpectedException {}; // 所有的 unexpected 异常对象被替换为这种类型对象
void convertUnexpected() // 如果一个 unexpected 异常被抛出,这个函数被调用
{
  throw UnexpectedException();
}

并以convertUnexpected函数替换缺省的unexpected函数,来使上述代码开始运行:set_unexpected(convertUnexpected);
任何非预期的异常都会调用convertUnexpected,于是UnexpectedException取代了unexpected,但是异常规格中没有包含UnexpectedException,那么terminate犹如未取代一样

(set_unexpected()使用详见http://www.cplusplus.com/reference/exception/set_unexpected/):

另一种把 unexpected 异常转变成知名类型的方法是替换 unexpected 函数,让其重新抛出当前异常,这样异常将被替换为bad_exception

void convertUnexpected() // 如果一个 unexpected 异常被
{ //抛出,这个函数被调用它只是重新抛出当前异常
throw;
}
set_unexpected(convertUnexpected);// 安装 convertUnexpected做为 unexpected的替代品

如果这么做,你应该在所有的异常规格里包含 bad_exception(或它的基类,标准类exception)。你将不必再担心如果遇到 unexpected 异常会导致程序运行终止。任何不听话的异常都将被替换为 bad_exception,这个异常代替原来的异常继续传递

总之exception specification对于"函数希望抛出什么样的exception"提供了说明,并且对于抛出exception specification中未指定行为的情况提供了默认行为.但它也有其缺点:编译器只对它们做局部性检验导致很容易违反,可能会更上层的exception函数处理未预期的exception等.使用exception specification一定要考虑他所带来的程序行为是否是真正你想要的


条款15 了解异常处理的成本

1.为了支持运行期处理exception,编译器需要做大量的簿记工作:确认如果发生异常所需要析构的对象,记录每个try语句块对应的catch子句及其能够处理的exception类型等.编译器还需要在运行期做一些对比工作:在exception抛出时适当析构对象并找出正确的catch子句等.可见exception的使用需要大量成本

2.不同编译器以不同的方法实现try语句块,使用try语句块的代码整体膨胀大约5%~10%,执行速度也会下降这个数

3.不使用exception的前提是程序及其所链接的程序库没有一个用到try,throw或catch.对于避免exceptions的程序库而言,这有利于编译器完成性能优化,然而也必须保证"client端抛出的exceptions绝不会传入程序库",这不仅会对"client重新定义程序库内的虚函数"带来妨碍,也会对"client定制callback函数"带来排挤影响.

4.为了使你的异常开销最小化,只要可能就尽量采用不支持异常的方法编译程序,把使用 try 块和异常规格限制在你确实需要它们的地方,并且只有在确为异常的情况下(exceptional)才抛出异常。

最新文章

  1. SVN为什么比Git更好
  2. Oracle设置自动递增的方法
  3. java 内存回收(GC)的方式
  4. Sizeof与Strlen的区别与联系(转)
  5. SQL Server 修复数据库 相关 脚本 之 DBCC CHECKDB 用法 来自同事分享
  6. Ext.grid.plugin.RowExpander的简单用法
  7. 【Mysql】安装 mysql-5.7.5 指南
  8. python和tk实现桌面番茄时间(1)
  9. Windows 8.1下使用IE 64位
  10. 对boost::shared_from_this的进一步封装
  11. cakephp , the subquery (2)
  12. OAuth2.0学习(1-7)授权方式4-客户端模式(Client Credentials Grant)
  13. C++虚拟多重继承对象模型讨论
  14. 全面解读Java NIO工作原理(2)
  15. 安装scrapy出错Failed building wheel for Twisted
  16. Delphi 数据导出到Excel
  17. PHP计算连续签到天数以及累计签到天数
  18. python执行方式及变量
  19. java面试复习题四
  20. Maven下CXF的wsdl2java生成客户端代码

热门文章

  1. genisoimage命令用法
  2. javascript里用php
  3. clientWidth offsetWidth scrollWidth
  4. 20145328 《Java程序设计》第4周学习总结
  5. ubuntu文字界面与图形界面切换
  6. openwrt下如何只编译uboot
  7. Mac系统安装MyEclipse
  8. 混合开发的大趋势之一React Native之页面跳转(2)+物理返回+特定平台代码
  9. 从零开始玩转JMX(四)——Apache Commons Modeler &amp; Dynamic MBean
  10. No module named _sqlite3 django python manage.py runserver