条款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)才抛出异常。

MoreEffectiveC++Item35(异常)(条款9-15)的更多相关文章

  1. MoreEffectiveC++Item35(效率)(条款16-24)

    条款16 谨记80-20法则 条款17 考虑使用 lazy evaluation(缓释评估) 条款18 分期摊还预期的计算成本 条款19 了解临时对象的来源 条款20 协助完成"返回值的优化 ...

  2. MoreEffectiveC++Item35(操作符)(条款5-8)

    条款5 对定制的"类型转换函数"保持警惕 条款6 区别increment/decrement操作符的前值和后置形式 条款7 千万不要重载&&,||,和,操作符 条款 ...

  3. MoreEffectiveC++Item35 条款27: 要求或禁止对象产生于heap中

    一 要求对象产生在heap中 阻止对象产生产生在non-heap中最简单的方法是将其构造或析构函数声明在private下,用一个public的函数去调用起构造和析构函数 class UPNumber ...

  4. MoreEffectiveC++Item35 条款26: 限制某个class所能产生的对象个数

    一 允许零个或一个对象 我们知道每当即将产生一个对象,我们有一个constructor被调用,那么我们现在想组织某个对象的产生,最简单的方法就是将其构造函数声明成private(这样做同事防止了这个类 ...

  5. MoreEffectiveC++Item35(基础议题)(条款1-4)

    条款1:区别指针和引用 条款2:最好使用C++转换操作符 条款3: 绝对不要以多态的方式处理数组 条款4: 避免无用的缺省构造函数 条款1:区别指针和引用 1.指针(pointer) 使用[*/-&g ...

  6. MoreEffectiveC++Item35 条款25 将constructor和non-member functions虚化

    1.virtual constructor 在语法上是不可将构造函数声明成虚函数,虚函数用于实现"因类型而异的行为",也就是根据指针或引用所绑定对象的动态类型而调用不同实体.现在所 ...

  7. 《More Effective C++ 》读书笔记(二)Exception 异常

    这事篇读书笔记,只记录自己的理解和总结,一般情况不对其举例子具体说明,因为那正是书本身做的事情,我的笔记作为梳理和复习之用,划重点.我推荐学C++的人都好好读一遍Effective C++ 系列,真是 ...

  8. oracle异常(-)

    一.概述异常分成三大类:预定义异常.非预定义异常.自定义异常处理方法分为:直接抛出异常.内部块处理异常.游标处理异常 预定义异常:由PL/SQL定义的异常.由于它们已在standard包中预定义了,因 ...

  9. Ng第十五课:异常检测(Anomaly Detection)

    15.1  问题的动机 15.2  高斯分布 15.3  算法 15.4  开发和评价一个异常检测系统 15.5  异常检测与监督学习对比 15.6  选择特征 15.7  多元高斯分布(可选) 15 ...

随机推荐

  1. 服务器抓包命令:tcpdump详解

    官网地址:http://www.tcpdump.org/tcpdump_man.html 简介: tcpdump,就是:dump the traffic on a network,根据使用者的定义对网 ...

  2. Flash Builder4注册机

    我的Eclipse下的Flash Builder 4正式版已经过期,之前在网上找到的注册码,都不能用了, 花了很久时间,才找到这个注册机. Flash Builder 4 注册机 Serial Cra ...

  3. nuget发布自已的程序集

    1.nuget注册并获取apikey 2.下载nuget.exe 3.设置apikey nuget setApiKey <apikey> 4.开发程序集 5.进入.csproj目录生成描述 ...

  4. ashx 方法模板

    ; ); //查询字符串拼接 string searchparams = DTRequest.GetQueryString("jsonstring"); fooddetail mo ...

  5. Rsync结合Inotify 实时同步配置(更新之前繁琐的传输认证)

    今天一位CU的友友根据之前介绍过 通过rsync+inotify-tools+ssh实现触发式远程实时同步  配置分发系统,但是由于认证繁琐,很容易出错,我今天重新整理了下,用rsync密码文件pas ...

  6. FileSystemWatcher监听文件是否有被修改

    作用:监听文件系统更改通知,并在目录或目录中的文件更改时引发事件. 需求:监听特定文件是否修改,然后做出相应的操作. 方法: ①利用一个线程,一直去查找该指定的文件是否有被修改,如果修改则操作特定步骤 ...

  7. MySQL优化具体

    1. 查询与索引优化分析 在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有慢查询日志,profiling分析,EXPLAIN分析查询,以及show命令查询系统状态及系统变量,通过定位分析 ...

  8. (转)C#调用C函数(DLL)传递参数问题

    备忘: 1.C函数参数为字符串char*.如果是入参,对应C#中string或StringBuilder:如果是出参对应C#中StringBuider: 2.C函数参数为结构体指针,需在C#中对应定义 ...

  9. js 代码执行时间

      <html> <head> </script> <script> var sTime=new Date().getTime(); alert(&qu ...

  10. MySQL表损坏修复【Incorrect key file for table】

    今天机房mysql服务器异常关机,重新启动后报错如下: -- :: [ERROR] /usr/local/mysql/bin/mysqld: Incorrect key file for table ...