Effective C++ (三) : 资源管理

 

资源管理

所谓资源就是:一旦用了它,就必须归还给系统。C++中最常使用的资源就是动态分配内存(若分配内存不归还,就会造成内存泄漏)。其它常见的资源包括文件描述器(fd)、互斥锁、数据库连接、网络sockets。

 

条款13:以对象管理资源

例:投资类型

class Investment {...};    //投资类型继承体系中的root class

factory函数供应特定的Investment对象

Investment* createInvestment(); //返回指针,指向Investment继承体系内的动态分配对象。调用者有责任删除​

f函数履行删除的责任

void f()
{
    Investment* pInv = createInvestment();          //调用factory函数
    ...
    delete pInv;                                    //释放pInv所指对象
}

在程序...区域内的语句过早return或者抛出异常可能导致最后delelte被略过,

为确保createInvestment返回的资源总是被释放,需要:把资源放进对象内,便可依赖C++的"析构函数自动调用机制”确保资源被释放。

 

auto_ptr是个"类指针对象“,也就是所谓的”智能指针“,其析构函数自动对其所指对象调用delete。下面示范:

void f()
{
    std::auto_ptr<Investment> pInv(createInvestment());     //调用fatory函数,一如既往使用pInv,
    ...                                                    //auto_ptr的析构函数自动删除pInv
}

 

 

"以对象管理资源“的两个关键想法:

-获得资源后立刻放进管理对象(managing object)内。以上代码中createInvestment返回的资源当做其管理者auto_ptr的初值。

-管理对象运用析构函数确保资源被释放。不论控制流如何如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放。

 

注意:由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr指向同一对象。

 

auto_ptr有个性质:

若通过copy构造函数或copy assignment操作符赋值它们。它们会编程null,而复制所得的指针将取得资源的唯一拥有权

 

std::auto_ptr<Investment> pInv1(createInvestment());             //pInv1指向createInvestment的返回物
std::auto_ptr<Investment> pInv2(pInv1);                         //现在pInv2指向对象,pInv1被设为Null

pInv1 = pInv2;                                      //pInv1指向对象,pInv2被设为Null

auto_ptr并非管理动态内存分配资源的神兵利器。例如:STL容器要求其元素发挥”正常的“复制行为,因此auto_ptr不合适。

 

替代方案是”引用计数型智慧指针“---shared_ptr:

void f()
{
    ...
    std::tr1::shared_ptr<Investment>
    pInv1(createInvestment());          
    
    std::tr1::shared_ptr<Investment>
    pInv2(pInv1);                               //pInv1和pInv2指向同一对象
    pInv1 = pInv2;
} 

auto_ptr和shared_ptr都在其析构函数内做delete而不是delete[],所以在动态分配的array上使用auto_ptr和shared_ptr是个馊主意。

 

请记住:

-为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。

-两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是最佳选择,因为其copy行为比较直观。auto_ptr的复制行为会使它们指向null.

 

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

 

使用C API函数处理类型为Mutex的互斥器对象,有lock和unlock两个函数:

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;
};

Mutex m;        //定义需要的互斥锁
...
{                       //建立区块用来定义critical section
    Lock m1(&m);        //锁定互斥器
    ...                 //执行操作
}                       //在区块末尾自动解除互斥器锁定

 

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

---禁止复制。许多时候允许RAII对象被复制并不合理。则禁止copying操作,将copying操作声明为private。

---对底层资源祭出”引用计数法“(reference-count)。将资源的”被引用数“递增,tr1::shared_ptr便是如此。

 

将Mutex* 改为 tr1::shared_ptr<Mutex>,便可实现Lock的reference counting。

对shared_ptr指定删除器,当引用次数为0时不是释放对象,而是解除锁定。

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
}

 //本例的Lock class未声明析构函数,class的析构函数(自己定义或者编译器生成)会自动调用其non-static成员变量(例中mutexPtr)的析构函数,即在shared_ptr的引用次数为0时调用删除器(unlock) 

 

请记住:

-复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为

-普遍而常见的RAII class copying行为是:抑制copying,施行引用计数法。

 

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

 

假如希望某个函数处理Investment对象,像这样:

std::tr1::shared_ptr<Investment> pInv(createInvestment());      
int daysHeld(const Investment* pi);             //返回投资天数

int days = daysHeld(pInv);          //错误,因为daysHeld需要Investment*指针,传给它的却是tr1::shared_ptr<Investment>对象。

shared_ptr和auto_ptr都提供一个get成员函数用来执行显式转换,会返回智能指针内部的原始指针:

int days = daysHeld(pInv.get());        //将pInv内的原始指针传给daysHeld


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

 

请记住:

-APIs往往要求访问原始资源,所以每一个RAII class应该提供一个”取得其所管理之资源“的办法。

-对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。

 

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

 

std::string* stringArray = new std::string[100];
...
delete stringArray;

错误:stringArray所含的100个string对象中的99个不太可能被适当删除,因为析构函数很可能没有被调用。

 

new:

-内存被分配出来  => 针对此内存会有一个构造函数被调用。

 

delete:

-针对此内存会有一个析构函数被调用  => 内存被释放出来

 

delete最大问题在于:
即将被删除的内存之内究竟存有多少对象?即将被删除的那个指针,所指的是单一对象或对象数组?

 

当你对着指针执行delete的时候,这些信息由你告诉它!:

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

 

请记住:

如果你调用new时使用[],你必须在对应调用delete时也使用[].如果你调用new时没有使用[],那么也不该在对应调用delete时使用[]。

 

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

假设有个函数处理程序的优先权,另一个函数在某动态分配所得的Widget上进行某些带有优先权的处理:

int priority();​
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

 谨记条款13的智慧铭言“以对象管理资源”,对动态分配来的Widget运用智能指针(这里采用tr1::shared_ptr)

processWidget(new Widget, priority());          //错误,不能通过编译

因为shared_ptr构造函数是explicit。所以无法将new Widget得到的指针隐式转换为tr1::shared_ptr.

改正:

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

在processWidget调用之前有三件事(参数列表):

-调用priortity

-执行 new Widget

-调用tr1::shared_ptr构造函数

 

 但是以什么次序执行呢?不确定    (new Widget 一定是在tr1::shared_ptr构造函数之前)

若最终是这样的操作序列:

-执行 new Widget

-调用priortity

-调用tr1::shared_ptr构造函数

 

在调用priority中导致异常,则new Widget返回的指针将遗失 ,引发资源泄漏。

 

避免此类问题的方法是:使用分离语句

1.创建Widget

2.将它置于智能指针内,然后传给processWidget

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

 

请注意:

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

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值