三.资源管理

资源就是,一旦使用便需要手动归还给系统。C++中的资源包括动态分配内存,文件描述器,互斥锁,数据库连接,图形界面中的笔刷和字型,以及网络sockets。这些资源都需要在使用后归还给系统。本章所讲述的内容便是介绍资源管理的办法,这基于构造函数,析构函数,copying函数的基础上。

条款13:以对象管理资源

1)

为确保资源总被释放,需要将资源放入对象内,当控制流离开后使对象的析构函数自动释放资源。这使用智能指针实现。

2)

i)获得资源后立刻放进管理对象。资源取得时机便是初始化时机(RAII); ii)管理对象运用析构函数确保资源被释放.

3)

i)auto_ptr在被copy构造函数或=复制时,它们会变为null,而复制所得的指针将取得资源的唯一拥有权; ii)auto_ptr的底层条件:受auto_ptr管理的对象资源必须绝对没有一个以上的auto_ptr指向它。 为此,使用“引用计数型智慧指针”代替auto_ptr,参见:

class Investment{  //资源类
...
void createInvestment() {...};
}
void f()
{
      ...
      std::tr1::shared_ptr<Investment> pInv(createInvestment()); 调用factory函数,经由shared_ptr析构函数自动删除pInv;
      std::tr1::shared_ptr<Investment> pInv1(createInvestment()); pInv1指向同一个对象
      pInv2(pInv1);
      pInv1 = pInv2; //pInv1和pInv2被销毁,他们所指的对象也就被自动销毁。
      ...
}

4)

没有为C++动态分配数组而设计的智能指针,因为用vector或者string总可以代替数组,若执意使用普通数组,那么请使用Boost。
请记住:

1.为防止资源泄漏,请使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源;
2.两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr,前者通常最佳选择,因为其copy行为比较直观,而auto_ptr复制动作会使他指向null。

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

当一个RAII对象被复制,会发生什么事?假定使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock函数可用:

void lock(Mutex* pm); //锁定pm的互斥器
void unlock(Mutex* pm); //解除互斥器锁定

为确保Mutex解锁,你建立了一个class用来管理机锁。这种管理由RAII对象支配:

class Lock{
public:
      explicit Lock(Mutex* pm) : mutexPtr(pm)
      {
            lock(mutexPtr);
      }
      ~Lock()
      {
            unlock(mutexPtr);
      }
private:
      Mutex *mutexPtr;
}

1)

禁止复制。许多时候允许RAII对象被复制并不合理,如互斥器类。因为很少能够拥有“同步化基础器物”的复件。禁止对此类资源的复制,便是将copying操作声明为private。对Lock类看起来是这样:

class Lock:private Uncopyable{  //禁止复制,见条款6
public:  
      ...
}

对底层资源使用“引用计数法”. 通常只要含有一个tr1::shared_ptr成员变量,RAII classes便可实现出reference-counting copying行为。如果前述的互斥器类Lock打算使用reference counting,它可以改变mutexPtr的类型,将他从Mutex改为tr1::shared_ptr,但是注意到tr1::shared_ptr的缺省行为是“当引用次数为0时删除其所指物”,这不是我们想要的,当我们使用上一个Mutex的时候,我们想要做的释放动作是解除锁定而非删除。因此我们需要使用tr1::shared_ptr的特性:允许指定所谓的删除器,这是一个函数或者函数对象,当引用次数为0时被调用(此方法auto_ptr没有)。删除器*对于tr1::shared_ptr而言是可有可无的第二参数,看起来是这样:

class Lock{
public:
      explicit Lock(Mutex *pm):mutexPtr(pm,unlock) //以某个Mutex初始化shared_ptr并以Unlock函数为删除器
      {
            lock(mutexPtr.get());
      }
private:
      std::tr1::shared_ptr<Mutex> mutexPtr;//使用shared_ptr替换raw pointer。
};

请注意到,本例中并没有额外声明析构函数,这是因为可以依赖编译器生成的缺省行为——tr1::shared_ptr在计数器为0时调用删除器。
复制底部资源. 复制资源管理对象时,应该进行“深度拷贝”。所谓“深度拷贝”指的是,在复制一个对象时,不论指针或其所指的内存都被制作出一个复件。
转移底部资源的拥有权.某些场合你需要保证永远只有一个RAII对象指向一个未加工资源。即使RAII对象被复制依然如此。
请记住:

1.复制RAII对象必须一并复制它所管理的资源,所以资源的coping行为决定了RAII对象的copying行为。
2.普遍而常见的RAII class copying行为是:抑制copying、使用引用计数法。

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

1)

见下代码段:

std::tr1::shared_ptr<Investment>pInv(createInvestment()); //
int daysHeld(const Investment* pi); //返回投资天数
int days = daysHeld(pInv); //无法通过编译

这是因为pInv是tr1::shared_ptr指针,而非Inestment指针。因此需要进行指针转换:显示转换或隐式转换。

2)

shared_ptr和auto_ptr都提供一个get成员函数,用来执行显示转换,也就是他会返回智能指针内部的原始指针的复件。

int days = daysHeld(pInv.get()); //可以通过编译

3)

此外shared_ptr和auto_ptr还重载了指针取值操作符(operatir->和operator*),它们允许隐式转换至底部原始指针:

class Investment{
public:
      bool isTaxFree() const;
      ...
};
Investment* createInvestment();
std::tr1::shared_ptr<Investment> pi1(createInvestment());
bool taxable = !(pi1->isTaxFree());
bool taxable2 = !(*pi1.isTaxFree());

3)

是否提供一个显示转换函数,将RAII class转换为底层资源,或是应该提供隐式转换,答案主要取决于RAII class被设计执行的特点工作。
请记住

1.APIs往往要求访问原始资源,所以RAII对象应该提供可以取得其所管理资源的办法。
2.对原始资源的转换分为隐式和显示转换,显示转换较为安全,但隐式转换对客户方便。

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

请记住

1.如果在new表达式中使用[],则在相应的delete中也应该使用[],如果你在new表达式中未使用[],一定不要在相应的delete表达式中使用[]。

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

1)

假设priority()处理程序的优先权,另一个函数用来对动态分配所得的Widget进行某些带有优先权的处理,见下代码段:

int priority();
void processWidget(std::tr1::shared_ptr<Widget>pw,int priority); 
//考虑调用processWidget;
processWidget(new Widget, priority());; //不能通过编译,实参类型不匹配
//转换类型;
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()) //可能造成资源泄漏

注意到

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority())

本句中的第一实参由两部分组成:
执行new Widget表达式
调用tr1::shared_ptr构造函数
编译时,编译器需完成的任务:
调用priority
执行new Widget表达式
调用tr1::shared_ptr构造函数
值得注意的是,C++在完成上述语句时,执行顺序不保证,但是new Widget语句会在tr1::shared_ptr之前的构造函数完成。所以可能有如下执行顺序:
执行new Widget表达式
调用priority
调用tr1::shared_ptr构造函数
当priority()调用失败时,可能会将new Widget返回的指针遗失,带来资源泄露。这是因为资源被创建和资源被转换为资源管理对象的两个时间点间有可能发生异常干扰。
正确的使用办法应该是:

std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

请记住

1以独立语句将newed对象放入智能指针内,如果不这样做,异常抛出时会导致难以察觉的资源泄露。

posted @ 2020-11-15 23:35  Viecgg  阅读(104)  评论(0)    收藏  举报