天天看点

Effective C++ 读书笔记(3.资源管理)条款13:以对象管理资源条款14:在资源管理类中小心copying行为条款15:在资源管理类中提供对原始资源的访问条款16 成对使用new和delete时要采用相同的形式条款17:以独立语句将newed对象置入智能指针

条款13:以对象管理资源

假设我们使用一个类来表示投资行为,各式各样的投资类型继承自一个root class Investment:

通过一个工厂函数供应我们的Investment对象:

调用createInvestment的对象后,有责任删除之。用一个f函数:

void f()
{	
	Inverstment * pInv=CreateInvestment();
	...
	delete pInv;
}
           

但是在发生如下情况的时候,f可能无法删除从CreateInvestment获得的对象。

  1. …区域中一句return
  2. 位于某个循环内,一旦break或者continue就无法执行delete
  3. …区域内抛出异常
  • 为了确保CreateInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f后自动调用析构函数自动释放资源。
  • 例1智能指针:auto_ptr(类指针对象)

    其析构函数自动对其所指的对象调用delete。

    void f()
    {
    	std::auto_ptr<Investment>PInv(CreateInvestment());//调用factory函数,使用Pinv
    	...
    }//f函数结束时经由auto_ptr的析构函数自动删除pInv
               

    获得资源后立即放入管理对象;

    管理对象运用析构函数确保资源被释放;

    注意:和指针一样,不要让多个auto_ptr指向同一个资源地址,这样释放资源是就会反复释放同一个对象,导致未定义行为。为了预防这个问题,auto_ptrs有一个不寻常的性质:如果通过copy构造函数或者copy assignment操作符复制他们,那么他们就会变成null,而复制之后所得的指针就会获得资源的唯一拥有权。

    std::auto_ptr<Investment>pInv1(CreateInvestment());
    std::auto_ptr<Investment>pInv2(pInv1);//pInv1变为null
    pInv1=pInv2;//pInv2变为null
               
  • 一种auto_ptr的替代方案就是使用“引用计数型智慧指针”(reference-counting smart pointer;RSCP)(tr1::shared_ptr):持续追踪有多少指针指向某个对象,当没有指针指向该对象时自动删除该资源。用法和auto_ptr差不多,但是复制操作可行。
  • auto_ptr和RSCP都是delete而不是delete[],所以不适用于指向数组。

条款14:在资源管理类中小心copying行为

上一条款描述了auto_ptr和tr1::shared_ptr如何将这个观念表现在heap-based资源上。然而并非所有资源都在heap_based上。

  • 例如:我们使用c API函数处理类型为Mutex的互斥器对象,有lock和unlock两个函数可以用。

    (Application Programming Interface,应用程序接口)是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。 用来提供应用程序与开发人员基于某软件或硬件得以访问的一组例程,而又无需访问源码,或理解内部工作机制的细节。

    void lock(Mutex* pm);//锁定pm所指的互斥器
    void unlock(Mutex* pm);//将互斥器解出锁定
               
    为了确保我么你不会忘记将一个被锁住的Mutex解锁,我们希望建立一个class来管理机锁。这个class的基本结构由RAII,资源在构造期间获得,在析构期间释放。
    class Lock{
    public:
    	explicit Lock(Mutex * pm):Mutexptr(pm){lock(MutexPtr);}
    	~Lock(){unlock(mutexPtr);}
    private:
    	Mutex* MutexPtr;
    };
    //客户对Lock的用法复合RAII方式
    Mutex m;//定义所需要的互斥器
    ...
    {//定义一个区块用来定义cirtical section(临界区,一次只能让一个进程访问的代码)
    	Lock m1(&m);//锁定互斥器
    	...//执行cirtical section内的操作
    }//在区块的最末尾,自动解除互斥器的锁定
               
  • 但当一个RAII对象被复制,会发生什么事情?
  1. 禁止复制

    条款6中用一个抽象基类,将copy和copy assignment都放在pirvate中再用派生类继承,这样就会禁止复制行为。

  2. 对底层资源祭出“引用计数法”(reference-count)

    有时候我们希望保有资源,直到他的最后一个使用者被销毁,这种情况下复制RAII对象时,应该将资源的被引用数递增,shared_ptr:

    如果Lock打算使用Reference counting,他可以改变mutexPtr的类型,将它从Mutex*改为std::shared_ptr < Mutex >,但是shared_ptr在引用数为零的时候执行的操作是删除对象,而Lock需要的是解锁操作,所以我们可以更改shared_ptr的删除器(第二个可有可无的参数)。

    class Lock{
    public:
    	explicit Lock(Mutex* pm):mutesPtr(pm,unlock)//第二个函数
    	{
    		lock(mutexPtr.get());
    	}
    private:
    	std::tr1::shared_ptr<Mutex> mutesPtr;
    };
               

条款15:在资源管理类中提供对原始资源的访问

资源管理类,排除泄漏。但许多APIs直接指涉资源,需要提供对原始资源的访问。

条款13中,使用auto_ptr和tr1::shared_ptr保存factory函数如createInvestment的调用结果。

有时候你希望处理某个函数如Investment对象,

int daysHeld(const Investmetn*pi);//返回投资天数

int days=dayHeld(Pinv)//不能通过编译
           

类型不匹配

  • 这时候你就需要一个函数可以将RAII class对象转化为其所内含原始资源。
  1. 显示转换:

    智能指针提供一个get()成员函数,用来执行显示转换,也就是会返回智能指针内部的原始指针。

  2. 隐式转换:

    operator->和operator*都可以隐式的转换至底部的原始指针。

注意

  1. APIs往往需要访问原始资源,所以每一个PAII class都需要提供一个“取得其所管理之资源”的办法。
  2. 对原始资源的访问可能经由显示转换(智能指针.get();用显示函数返回资源)或者隐式转换(->或者*,隐式函数返回资源),一般而言显示转换比较安全。但隐式转换对客户比较方便。 、

条款16 成对使用new和delete时要采用相同的形式

  • new配delete
  • new[]配delete[]
  • 对数组使用typedef(不建议)需要delete[]

条款17:以独立语句将newed对象置入智能指针

假设我们有一个函数来揭示处理函数的优先权,另一个函数用来在某动态分配所得的Widget上进行某些带有优先权的处理

int pirority();
void processWiget(std::tr1::shared_ptr<widget>pw,int priority);
           

用对象管理资源,调用processWidget

上述代码将不能通过,由于shared_ptr构造函数需要一个原始的指针,而其构造函数又是explicit,不允许隐式转换,所以我们需要显示地将原始指针转换为shared_ptr。

  • 接下来我们考虑这个函数各个参数的处理过程:
  1. priority()
  2. new Widget
  3. tr1::shared_ptr构造函数

    我们知道new Wiget一定在shared_ptr构造函数之前调用,但priority()函数有可能在任意顺序被调用,一旦在中间而且出现异常,那么new Widget所获得的指针将会遗失:“资源被创建”到“资源被转换为资源管理对象”过程中发生异常

    避免这一类问题的方法就是使用分离语句,先将new Widget放入智能指针,再将指针指针传递给函数参数。

继续阅读