Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1401458
  • 博文数量: 842
  • 博客积分: 12411
  • 博客等级: 上将
  • 技术积分: 5772
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-14 14:43
文章分类

全部博文(842)

文章存档

2013年(157)

2012年(685)

分类: C/C++

2013-07-26 17:50:41

原文地址:C++之资源管理 作者:scq2099yt

        所谓资源就是一旦使用完,就必须将其归还给系统,否则就有你好果子吃。在C++总,常用的资源包括动态分配内存、文件描述符、互斥锁、UI中的字型和笔刷、数据库连接,网络socket等。接下来本文介绍几种管理资源的方式,以解决资源泄露或回收问题。
一、以对象管理资源

         假设我们使用一个用来朔模投资行为的程序库,其中各式各样的投资类型继承自一个root class Investment:
        class Investment {...};        //投资类型继承体系中的root class
        进一步假设,这个程序库通过一个工厂函数供应我们某个特定的Investment对象:
        Investment* createInvestment();        
        上面函数返回一个指针,指向Investment继承体系内的动态分配对象,调用者有责任删除它。假设有个函数f履行了这个责任:
        void f()
        {
                Investment* pInv = createInvestment();        //调用工厂函数
                ...
                delete pInv;                                                //释放pInv所指对象
        }
        咋一看上面天衣无缝,函数f能够自动删除它从createInvestment获得的投资对象,但是事实上危机重重:
        (1)由于疏忽或者后期接收者维护代码导致“...”区域内的一个过早的return语句或goto语句。
        (2)“...”区域内的语句抛出异常。
        上面的任意情况发生,都会导致因无法执行到delete语句而内存泄露。事实上,函数f中将资源的管理依赖于人治,所以悲剧地发生只是时间问题。
        为了解决如上问题,需要将返回的资源放进对象内,以对象来管理资源,当控制流离开f,该对象的析构函数会自动释放那些资源。
1、auto_ptr
        许多资源被动态分配于堆上而后被用于单一区块或函数内,资源应该在控制流离开这个区块或函数时被释放,标准程序库提供的auto_ptr正是针对这种形式而设计的特制产品。
        auto_ptr是一个“类指针(pointer-like)对象”,也就是所谓的智能指针,其析构函数自动对其所指对象调用delete。下面示范如何使用auto_ptr以避免函数f潜在的资源泄露可能性:
        void f()
        {
                std::auto_ptr pInv(createInvestment());
                ...
        }
        函数f中调用工厂函数产生投资对象,并将对象由auto_ptr管理,然后一如既往地使用pInv,在控制流离开函数f时,由auto_ptr的析构函数自动删除pInv。
        上面代码中createInvestment返回的资源被当做其管理者auto_ptr的初值,这种方式被称为“资源获得时机便是初始化时机”(Resource Acquisition Is Initialization, RAII)。
        需要注意的是:如果通过copy构造函数或copy assignment操作符复制auto_ptr,则其会变成NULL,而复制所得的指针将取得资源的唯一拥有权,也就是只能有一个auto_ptr对象管理资源,比如:
        std::auto_ptr pInv1(createInvestment());     //pInv1指向createInvestment返回物
        std::auto_ptr pInv2(createInvestment());     //pInv2指向对象,而pInv1被设为NULL
        pInv1 = pInv2;                                                              //现在pInv1指向对象,而pInv2被设为NULL
        受auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它,这意味着auto_ptr并非管理动态分配资源的神兵利器,比如STL容器就要求其元素发挥“正常的”复制行为,因此就用不上auto_ptr。
2、shared_ptr
        auto_ptr的替代方案是“引用计数型智能指针”(referece-counting smart pointer, RCSP)。所谓RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。RCSP提供的行为类似垃圾回收(garbage collection),不同的是RCSP无法打破环状引用(cycles of references,比如:两个其实已经没被使用的对象彼此互指,因而好像还处在“被使用”状态)。
        TR1的tr1::shared_ptr就是一个RCSP,所以函数f的又一版本来了:
        void f()
        {
                std::tr1::shared_ptr pInv(createInvestment());    //在离开函数时,由shared_ptr析构函数自动删除pInv
                ...
        }
        这与auto_ptr版本基本一致,但shared_ptr的复制行为正常多了:
        void f()
        {
                std::tr1::shared_ptr pInv1(createInvestment);        //pInv1指向createInvestment返回物
                std::tr1::shared_ptr pInv2(pInv1);                         //pInv2和pInv2指向同一个对象
                pInv1 = pInv2;                                                                        //同上,无任何改变
                ...
        }
        在函数f控制流结束时,pInv1和pInv2被销毁,其所指对象也就被自动销毁。由于tr1::shared_ptr的复制行为“一如预期”,它可被用于STL容器以及其它“auto_ptr之非正统复制行为并不适用”的语境上。
        但是auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[],这意味其不能用于动态分配的数组,而boost::scoped_array和boost::shared_array class则能提供这种行为。

二、资源管理类中小心coping行为
        我们在使用C API函数处理类型为Mutex的互斥量对象(mutex object)时,一般会提供lock和unlock函数:
        void lock(Mutex* pm);        //锁定pm所指的互斥量
        void unlokc(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;                   //定义你需要的互斥量     
        ...                             
        {                              //建立一个区块用来定义critical section
            Lock ml(&m);         //锁定互斥量
            ...                        //执行critical section内的操作
        }                             //在区块最末尾,自动解除互斥量锁定
        上面代码看似很完美,但是如果Lock对象被复制,又将如何呢?
        Lock m11(&m);         //锁定m
        Lock m12(ml1);         //将ml1复制到ml2身上,会发送什么呢?
        一般为了防止复制RAII对象带来的副作用,会采取禁止复制的方式,也就是将coping操作声明为private,包括拷贝构造函数和赋值操作符。        
            
三、承诺使用new和delete是要采取相同型式
        成对使用new和delete时要采用相同型式,比如:如果你在new表达式中使用[],必须在相应的delete表达式中也使用[],否则的话其结果未定义(在delete时可能会导致太少的析构函数被调用);如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。
        为什么会如此,因为在new单一对象和对象数组时内存布局会不同,可能的布局如下:
        单一对象 object
        对象数组 n | object |object | ...
        其中,n表示数组大小,当然不同的编译器做法不尽相同。

四、独立语句将newed对象植入智能指针
        假设我们有个函数用来揭示处理程序的优先权,另一个函数用来在某动态分配所得的widget上进行某些带优先权的处理:
        int priority();
        void processwidget(std::tr1::shared_ptr pw, int priority);
        假设我们如此调用:
        processwidget(std::tr1::shared_ptr(new widget), priority());
        上面代码看似用对象管理资源,但是事实上确还是有可能泄露资源。因为编译器在产出一个processwidget调用码之前,会先核算即将被传递的各个实参,并做如下几件事情:
        (1)调用函数priority
        (2)执行new widget
        (3)调用tr1::shared_ptr构造函数
        但C++编译器不一定会按照上面顺序来生成代码,最终可能的操作序列会是:
        (1)执行new widget
        (2)调用priority函数
        (3)调用tr1::shared_ptr构造函数
        好吧,问题来了,如果在调用priority函数时产生异常,则new widget返回的指针会被遗失,因为其尚未被置入tr1::shared_ptr内,从而引发资源泄露。
        避免产生这种问题的方法很简单:使用分离语句,分别写出(1)创建widget;(2)将它置入一个智能指针内,然后将智能指针传给processwidget函数:
        std::tr1::shared_ptr pw(new widget);        
        processwidget(pw, priority());
        以独立语句将newed对象存储于(置入)智能指针内,如果不这样做,一旦异常抛出,就有可能导致难以察觉的资源泄露。

            
        
            
        
阅读(3053) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~