Effective c++ 第三章(条款13-17)

5 篇文章 0 订阅

第三章:资源管理

资源管理

条款13:以对象管理资源(Use objects to manage resources)

class Investment{...};                   //创建一个类
Investment* creatInvestment();           //使用factory function
void f(){
  Investment* pInv = creatInvestment();  //调用factory函数
  ...
  delete pInv;                           //释放pInv所指对象
}

看似这个函数是正确的,创建一个指针对象,结束时delete掉,但是在“…”区域内可能会有一个过早的return语句(或者抛出异常),这时delete语句将不会执行

我们可以倚赖c++的"析构函数自动调用机制"来确保资源被释放
标准程序库提供的"智能指针":auto_ptr

void f(){
  std::auto_ptr<Investment> pInv(creatInvestment());
  ...               
}                  //经由auto_ptr的析构函数自动删除pInv

获得资源后立刻放进管理对象内,“资源取得时机便是初始化时机”(Resource Acquisition Is Initialization;RAII),由于auto_ptr被销毁时会自动删除它所指之物,所以别让多个auto_ptr同时指向同一对象。一旦对象被销毁,其析构函数会被自动调用所以小心异常(见条款08)
当通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得指针将取得资源的唯一拥有权!!

  std::auto_ptr<Investment> pInv1(creatInvestment()); //pInv1指向对象
  std::auto_ptr<Investment> pInv2(pInv1);             //pInv2指向对象,pInv1被设为null
  pInv1=pInv2;                                        //pInv1指向对象,pInv2被设为null

我们可以使用“引用计数型智慧指针”(reference-counting smart pointer;RCSP),追踪共有多少对象指向资源,并在无人指向它时自动删除该资源。trl::shared_ptr就是个RCSP

void f(){
 std::trl::shared_ptr<Investment> pInv(creatInvestment());
  ...               
}                     //经由shared_ptr的析构函数自动删除pInv

当我们再次通过copy构造函数或copy assignment操作符复制它们时:

 std::trl::shared_ptr<Investment> pInv1(creatInvestment()); //pInv1指向对象
 std::trl::shared_ptr<Investment> pInv2(pInv1);             //pInv1和pInv2指向同一个对象
 pInv1=pInv2;               //同上

我们需要注意这两者在其析构函数中调用的是delete而非delete[]

std::auto_ptr<std::string> aps(new std::string[10]);    //错误!!使用错误的delete形式
std::tlr::shared_ptr<int> spi(new int[1024]);           //同上

请记住:
-为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源
-l两个常被使用的RAII class分别是tlr::shared_ptr和auto_ptr,前者的copying行为比较直观,auto_ptr复制会使被复制物指向null

条款14:在资源管理类中小心copying行为(Think carefully about copying behavior in resource-managing classes)

class Lock{
public:
   explicit Lock(Mutex* pm): nutexPtr(pm){   //获得资源
      lock(mutexPtr);
   }
   ~Lock(){unlock(mutexPtr);}                //释放资源
private:
   Mutex* mutexPtr;
};
Mutex m;
Lock ml1(&m);            //锁定m
Lock ml2(ml1);           //将ml1复制到ml2??

这里我们由两种选择:
1.禁止复制

class Lock: private Uncopyable{     //禁止复制--条款06
public:
   ...                              //同前
};

2.使用"引用计数法"->trl::shared_prt : trl::shared_prt的缺省行为是”当引用次数为0时删除其所指物“,这时我们要使用”删除器“,当引用次数为0时被调用

class Lock{
public:
   explicit Lock(Mutex* pm): nutexPtr(pm,unlock){   //以unlock函数为删除器
      lock(mutexPtr.get());                     
   }
    //~Lock(){unlock(mutexPtr);}       
private:
   std::trl::shared_prt<Mutex> mutexPrt;    
};

可以不用声明析构函数,条款05:class析构函数会自动调用其non-static成员变量(mutexPrt)的析构函数

请记住:
-复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
-普遍而常见的RAII class copying 行为是:抑制copying、施行引用计数法。其他行为也可以实现

条款15:在资源管理类中提供对原始资源的访问(Provide access to raw resources in resource-managing classes)

 std::trl::shared_ptr<Investment> pInv(creatInvestment()); 
 int daysHeld(const Investment* pi);
 int days=daysHeld(pInv);               //错误

daysHeld需要Investment* 指针,而传递的却是类型为std::trl::shared_ptr<…>的对象,这里我们需要将RAII class对象转换为其所含原始资源,有显式转换和隐式转换。
1.显式转换:
通过提供的get成员函数

int days=daysHeld(pInv.get()); 

或者使用重载运算符(operator->)、(operator*)允许隐式转换到底部原始指针

2.提供一个隐式转换函数

FontHandle getFont();
void releaseFont(FontHandle fh);
class Font{
public:
   explicit Font(FontHandle  fh): f(fh){ //pass-by-value
   }
   ~Font(){releaseFont(f);}
private:
   FontHandle f;
};

显式转换:

class Font{
public:
   ...
   FontHandle get() const {
      return f;         //显式转换
   }          
   ...
};

void changeFontSize(FontHandle f,int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(),newFontSize);          //显式的转换

隐式转换:

class Font{
public:
   ...
   operator Fonthandle() const{
      return f;        //隐式转换
   }       
   ...
};
...
changeFontSize(f,newFontSize);            //将Font隐式转换为Fonthandle

但是隐式转换会增加错误发生机会

Font f1(getFont());
...
FontHandle f2=f1;        //原意要拷贝一个Font对象,却反而f1隐式转换为其底部的Fonthandle,然后复制它

请记住:
-APIs往往要求访问原始资源,所以每一个RAII class应该提供一个”取得其所管理之资源“的办法
-对原始资源的访问可能经由显示转换或隐式转换。一般显式转换较为安全,但隐式转换对客户比较方便

条款16:成对使用new和delete时要采用相同形式(Use the same form in corresponding uses of new and delete)

std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1;                   //删除一个对象
delete[] stringPtr2;                 //删除一个由对象组成的数据

我们应该对不同的的new使用其所相应的delete

单一对象
Object
对象数组
n
Object
...

对象数组会告诉你数组大小,从而需要调用几次析构函数,当我们混合使用则结果亦为定义。例对stringPtr1 使用delete[],可能销毁的并不是此数组,但是传递了一个数组大小,进而删除了其他内存,可能会造成内存问题!!

同时对于typedef也要小心使用

typedef std::string AddressLines[4];
std::string* pal = new AddressLines;
delete pal;                //错误
delete[] pal;              //很好

所以为了他人能够更好的理解,我们应该避免这种写法(不是不行)
请记住:
-如果你在new表达式中使用了 [] ,必须在相应的delete表达式中也使用 [] .如果你在new表达式不使用了 [] ,一定不要在相应的delete表达式中使用 []

条款17:以独立语句将newed对象置入智能指针(Store newed objects in smart pointers in standalone statements)

假设有如下函数

int priority();
void processWidget(std::tlr::shared_prt<Widget> pw,int priority);

当我们使用时

processWidget(new Widget,priority());     //错误

无法通过编译!因为tlr::shared_prt构造函数需要一个原始指针,且该构造函数是个explicit构造函数,无法隐式转换。

processWidget(std::tlr::shared_prt<Widget>(new Widget),priority()); //正确

现在第二个问题是编译器对processWidget产出的调用码。首先必须核算被传递的各个实参,第一个实参由两部分组成(1.执行"new Widget"表达式),(2.调用trl::shared_prt构造函数),第二个实参就只有对priority函数的调用

调用priority
执行new Widget
调用trl::shared_prt构造函数

然而编译器以什么次序完成不一致,但首先可以肯定执行new Widget一定在调用trl::shared_prt构造函数前面

现在有三中执行顺序:

执行new Widget
调用trl::shared_prt构造函数
调用priority

调用priority放在第一和第三位置都没有问题,创建一个指针对象,然后将其置入trl::shared_prt内管理。

然而第三种却可能发生问题:

执行new Widget
调用priority
调用trl::shared_prt构造函数

当放在中间对priority的调用抛出异常时,"new Widget"返回的指针尚未被置入trl::shared_prt内,从而可能引发资源泄漏

我们解决这个问题的办法是使用分离语句

std::trl::shared_ptr<Widget> pw(new Widget);    //在单独语句内以智能指针存储newed所得对象
processWidget(pw,priority())                    //这样调用绝不至于造成泄漏

请记住:
-以独立语句将newed对象存储与智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值