资源管理

条款13: 以对象管理资源

【Problem】

void f()
{ 
   Investment *pInv = createInvestment(); 
         ...  /* 这里存在诸多“不定因素”,造成 "delete pInv"得不到执行,存在内存泄露的可能。*/
   delete pInv;
} 

【Solution. 1】

使用auto_ptr智能指针确保资源的释放,避免内存泄露

void f(){ 
   std::auto_ptr<Investment> pInv(createInvestment()); 
   ...   
   /*函数退出之前,经由auto_ptr的析构函数自动删除pInv指针*/
}        

【Key Points】

“以对象管理资源”的两个关键想法:
1. 获得资源后立刻放进管理对象内(如auto_ptr)
2. 管理对象运用析构函数确保资源被释放

【Attention】

auto_ptr的限制,举例:

std::auto_ptr<Investment> 
    pInv1(createInvestment()); //pInv1指向createInvestment()返回值;
std::auto_ptr<Investment> 
    pInv2(pInv1);              //pInv2指向对象,而pInv1被设为NULL;
pInv1 = pInv2;                 //pInv1指向对象,而pIn2被设为NULL;

受auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它。
STL容器要求其元素发挥正常的复制行为,因此这些容器容不得auto_ptr。

【Solution.2】

使用share_ptr智能指针(引用计数型智慧指针)

void f()
{ 
    ... 
    std::tr1::shared_ptr<Investment>  
        pInv1(createInvestment()); //pInv1指向createInvestment()返回物;
    std::tr1::shared_ptr<Investment>  
        pInv2(pInv1); //pInv1,pInv2指向同一个对象;
    pInv1 = pInv2;    //同上,无变化
    ... 
}    //函数退出,pInv1,pInv2被销毁,它们所指的对象也竟被自动释放。

【Attention】
1. auto_ptr和tr1::share_ptr 两者都是在其析构函数内做delete而不是delete[];意味着在动态分配的array身上使用两者是个坏主意。

2. share_ptr不能管理循环引用的对象,否则造成内存泄露,举例:

class parent;
class child;

typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<child> child_ptr;

class parent
{
public:
    children_ptr child;
    ~parent() { std::cout <<"destruct parent."<<std::endl; }
};

class child
{
public:
    parent_ptr parent;
    ~child() { std::cout <<"destruct child."<<std::endl; }
};

int main(){
    parent_ptr pat(new parent()); //(pat 计数 = 1)
    children_ptr chd(new child()); //(chd 计数 = 1)
    pat->child = chd; //(chd 计数 = 2)
    chd->parent = pat; //(pat 计数 = 2)
    return 0; // (函数退出时,减一后仍然大于0,无法执行析构函数,造成内存泄露)
}

解决方法:
1. 当parent的生存期超过childr的生存期的时候,child内改为使用一个普通指针指向parent。
2. 使用弱引用的智能指针打破这种循环引用。举例:

暂略

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

【Attention】

1. 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
2. 普遍而常见的RAII类拷贝行为是:抑制拷贝,施行引用计数法。不过其它行为也可能被实现

【Solution】

“当一个RAII对象被复制,会发生什么事?”大多数时候你会选择一下两种可能:

1. 禁止复制。如果复制动作对RAII类并不合理,你便应该禁止之。利用条款6(禁止类的copying函数)。
2. 对底层资源使用”引用计数法“。有时候我们又希望保有资源,直到它的最后一个使用者被销毁。 利用shared_ptr
3. 转移底部资源的拥有权。 利用auto_ptr
4. 复制底部资源。 利用深拷贝
``

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

【Problem】
需要一个函数可将RAII对象(shared_ptr)转换为其所内含之原始资源.

【Solution】
有两种做法可以达成目标:
1. 显示转换
shared_ptr和auto_ptr提供一个get成员函数,用来执行显示转换,也就是返回智能指针内部的原始指针。
shared_ptr和auto_ptr重载指针取值操作符(operator->和operator*),它们允许隐式转换至底部原始指针。
(即在对智能指针对象实施->和*操作时,实际被转换为被封装的资源的指针。)

2. 隐式转换
隐式转换可能引起“非故意之类型转换”

RAII class 设计方式

class Font
{
public:
    ...
    FontHandle get() const//FontHandle是资源;显示转换函数
    {
        return f;
    }
    operator FontHandle() const//隐式转换这个值得注意,可能引起“非故意之类型转换”
    {
        return f;
    }
private:
    FontHandle f; // 资源
    ...
};

尽量使用显示转换,减少出错的可能

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

如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。
如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

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


shiyang6017
158 声望59 粉丝