AKever

导航

C++(1) 智能内存管理计数-智能指针

一、智能内存管理计数-智能指针(来自Cocos2dx的抄录)

目前,主要有两种实现智能管理内存的技术,一是引用计数,一是垃圾回收

引用计数:它是一种很有效的机制,通过给每个对象维护一个计数引用计数器,记录该对象当前被引用的次数。当对象增加一次引用时,计数器+1;而对象失去一个引用时,计数器-1;当引用计数为0时,标志着该对象的生命周期结束,自动触发对象的回收释放。引用计数的重要规则是每个程序片段必须负责任地维护引用计数,在需要维持对象生存的程序段的开始和结束分别增加和减少一次引用计数,这样我们就可以实现十分灵活的智能内存管理。实际上,这与 new 和 delete 的配对使用十分类似,但是很巧妙地将生成和回收的事件转换成了使用和使用结束的事件。对程序员来说,维护引用计数比维护生命周期信息轻松了许多了。引用计数解决了对象的生命周期管理问题,但堆碎片化和管理烦琐的问题仍然存在

垃圾回收:它是通过引入一种自动的内存回收器,试图将程序员从复杂的内存管理任务中完全解放出来。它会自动跟踪每一个对象的所有引用,以便找到所有正式使用的对象,然后释放其余不再需要的对象。垃圾回收回收器还可以压缩使用中的内存,以缩小堆所需要的工作空间。垃圾回收可以防止内存泄露,有效地使用可用的内存。但是,垃圾回收器通常作为一个单独的低级别线程运行的,在不可预知的情况下对内存堆中已经死亡的或长时间没有使用过的对象进行清除和回收,程序员不能手动指派垃圾回收器回收哪个对象。回收机制包括分代复制垃圾回收标识垃圾回收增量垃圾回收

 ---------------引用计数---------垃圾回收---------end-----------------------

 

二、Cocos2d-x和Object-C一致,采用了引用计数和自动回收的内存管理机制。

1>引用计数:为了实现对象的引用计数记录,Cocos2d-x实现了自己的根类CCObject, 引擎中的所有类都派生自CCObject.具体的情况,可以查看CCObject中的定义,如下:

class CC_DLL CCObject : public CCCopying
{
public:
         //对象id, 在脚本引擎中使用
         unsigned int m_uID;
         //Lua中的引用ID,同时被脚本殷勤使用
         int m_nLuaID;
protected:
         //引用数量
         unsigned int m_uReference;
         //标识此对象是否已设置为autorelease
         bool m_bManaged;
public:
         CCObject(void);
         virtual ~CCObject(void);
         void release(void);
         void retain(void);
         CCObject* autorelease(void);
         CCObject* copy(void);
         bool isSingleRefrence(void);
         unsigned int retainedCount(void);
         virtual bool isEqual(const CCObject* pObject);
         virtual void update(ccTime dt) {CC_UNUSED_PARAM(dt); };
         friend class CCAutoreleasePool;
};    

 应用测试:

fish = new CCSprite();

fish->init();
CCLog("after init: %d", fish->retainCount());
fish->retain();
CCLog("after retain: %d", fish->retainCount());
fish->release();
CCLog("after release: %d", fish->retainCount());
fish->autorelease();
CCLog("after autorelease: %d", fish->retainCount());

得到的结果:

after init: 1
after retain: 2
after release: 1
after autorelease: 1

----------------------引用计数---------------------end-------------------------------------------------

 

2>自动回收:Cocos2d-x通过回收池管理器 CCPoolmanager(单例对象)的push()和pop()方法来创建或释放回收池。

通常,Cocos2d-x会在每一帧之间释放一次自动回收池

CCPoolManager::sharePoolManager()->push();
for(int i=0; i<n; i++) 
{
  CCString* dataItem = CCString::create("%d", Data[i]);
  stringArray->addObject(dataItem)
}
CCPoolManager::sharePoolManager()->pop();

以上看出自动回收池是可嵌套的。通常,引擎维护者一个回收池,所有的autorelease对象都添加到了这个池中。多个自动回收池排列成栈结构,当我们手动创建回收池后,回收池会压入栈的顶端,autorelease对象仅添加到顶端的池中。当顶层的回收池被弹开释放时,他内部的对象会被释放一次,此后出现的autorelease对象则添加到下一个池中。

在自动回收池套嵌的情况下,每个对象是如何加入自动回收池以及如何释放的,相关代码如下:

//步骤A
obj1->autorelease();
obj2->autorelease();
//步骤B
CCPoolManager::sharedManager()->push();
//步骤C
for(int i=0;i<n;i++)
{
     obj_array[i]->autorelease();  
}
//步骤D
CCPoolManager::sharedPoolManager()->pop();
//步骤E
obj3->autorelease();

**上述代码的具体过程下图所示
步骤A,obj1与obj2被加入到回收池1中,如图2-7a所示;
步骤B创建了一个新的回收池,此时回收池2接管了下来的所有的autorelease();
步骤C是一个循环,其中把n个对象加入到回收池2中;
步骤D释放了回收池2, 因此回收池2中的n个对象都被释放了一次,同时回收池1接管autorelease()操作;
步骤E调用了obj3的autorelease()方法, 把obj3加入回收池中。

 -----------------------------自动回收------------------------------end---------------------------------------------

 

3>Cocos2d-x中的工厂模式与内存管理

CCObject* factoryMethod() {   
      CCObject* ret = new CCObject();   
      //在这里对 ret 对象进行必要的初始化操作   
     return ret;   
}  

这段看起来正常的代码其实隐藏着一个问题:工厂方法对 ret 对象的引用在函数返回时已经结束,但是它没有释放对 ret的引用,埋下了内存泄露的隐患。但是,如果在函数返回前就执行 release(),这显然是不合适的,因为这会触发对象的回收,再返回的对象指针就成为了错误指针。

autorelease()方法很好地解决了这个问题。此函数结束时我们已经丧失了对 ret 的引用,为了把 ret 对象传递给接受者,需要对它进行一次 autorelease 操作,这是因为虽然我们调用了 autorelease 方法,但是对象直到自动回收池释放之前是不会被真正释放掉的(通常 Cocos2d-x 会在每一帧之间释放一次自动回收池),调用者有足够的时间来对它进行 retain操作以便接管 ret 对象的引用权。因此,Cocos2d-x 的执行机制很巧妙地保证了回收池中的对象不会在使用完毕前释放。利用autorelease()修改后的工厂方法如下:

CCObject* factoryMethod() {   
      CCObject* ret = new CCObject();   
      //这里对 ret 对象进行必要的初始化操作   
      ret->autorelease();   
      return ret;   
}  

在 2.2 节中,我们曾提到两种创建对象的方式。使用构造函数创建对象时,对象的引用计数为 1,因此调用者需要在使用完毕后谨慎地释放对象;使用工厂方法创建对象时,虽然引用计数也为 1,但是由于对象已经被放入了回收池,因此调用者没有对该对象的引用权,除非我们人为地调用了 retain()来获取引用权,否则,不需要主动释放对象。

---------------------工厂模式与内存管理------------------end--------------------------------------------

 

4>对象传递 -- retain & release

将一个对象赋值给某一指针作为引用的时候,为了遵循内存管理的原则,我们需要获得新对象的引用权,释放旧对象的引用权。此时,release()和 retain()的顺序是尤为重要的。首先来看下面一段代码:

void SomeClass::setObject(CCObject* other) {   
    this->object->release();   
    other->retain();   
    this->object = other;   
}  

这里存在的隐患是,当 other和 object 实际上指向同一个对象时,第一个 release()可能会触发该对象的回收,这显然不是我们想看到的局面,所以应该先执行 retain()来保证other 对象有效,然后再释放旧对象:

void SomeClass::setObject(CCObject* other) {   
    other->retain();   
    this->object->release();   
    this->object = other;   
}  

其他可行的解决方案也有很多,例如使用 autorelease()方法来代替 release()方法,或在赋值前判断两个对象是否相同。在 Google的 Objective-C 编程规范中,推荐使用 autorelease()方法代替 release()方法。
---------------------对象传递---------------------end----------------------------

 

5>释放:release()还是 autorelease()? 

上面的两个例子实际上提出了一个问题:在使用 autorelease()可以达到与release()同样的效果,甚至还能避免 release(), 的许多隐患的情况下,是不是应该完全用 autorelease()代替 release()呢?

实际上,autorelease()并不是毫无代价的,其背后的垃圾池机制同样需要占用内存和 CPU资源,每次执行 autorelease(), 的过程,实际上对应的是执行成对的 retain()和release(),以及一次成对的容器存取,还包括其他的逻辑判断。过多不必要的 autorelease()将导致垃圾池臃肿膨胀,在存在大量内存操作的程序中会尤为严重地挤占本来就紧张的系统资源。

此外,autorelease()只有在自动释放池被释放时才会进行一次释放操作,如果对象释放的次数超过了应有的次数,则这个错误在调用 autorelease()时并不会被发现,只有当自动释放池被释放时(通常也就是游戏的每一帧结束时),游戏才会崩溃。在这种情况下,定位错误就变得十分困难了。例如,在游戏中,一个对象含有 1 个引用计数,但是却被调用了两次autorelease()。在第二次调用 autorelease()时,游戏会继续执行这一帧,结束游戏时才会崩溃,很难及时找到出错的地点。

因此,我们建议在开发过程中应该避免滥用 autorelease(),只在工厂方法等不得不用的情况下使用,尽量以 release()来释放对象引用。
------------------------5>释放:release()还是 autorelease()? -------------------end--------------------------

 

 

 

 

 

posted on 2014-01-20 14:17  AKever  阅读(721)  评论(0)    收藏  举报