從智能指針聯(lián)想到cocos2dx內(nèi)存管理機制

1. 寫在前面

C++在C11之前,都是使用new分配內(nèi)存delete釋放內(nèi)存。這看起來是不是非常輕松?其實這會暴露出許多缺點

  1. 內(nèi)存重復(fù)釋放
  2. 野指針:指向的內(nèi)存已經(jīng)被釋放了,但是指針還在使用
  3. 內(nèi)存泄露:不再使用的內(nèi)存沒有釋放,內(nèi)存占用率過高

C11就引出了智能指針來解決以上的三個問題。
我已經(jīng)有博客提到智能指針了,想多了解的朋友可以去看看:C++多線程下的shared_ptr、C11新特性之智能指針
shared_ptr:多個智能指針可共享同一內(nèi)存,使用引用計數(shù)
unique_ptr:不能和其他智能指針指向同一個內(nèi)存,直接賦值給其他指針會報錯,但是可以使用move來轉(zhuǎn)移,轉(zhuǎn)移后之前的智能指針就失效。
week_ptr:使用lock()函數(shù),它可以檢測weak_ptr訪問的對象是否存在

既然智能指針看起來解放了大家去釋放內(nèi)存的步驟,那么為什么在cocos里面我們不使用智能指針來管理內(nèi)存呢?

  1. 性能損失
  2. 仍要顯示聲明指針

2. Ref

cocos2dx雖然沒有使用智能指針,但是它用了一個與智能指針非常相似的東西,也就是下面我們要說的Ref。
Ref的主要流程
當對象被創(chuàng)建時候,引用計數(shù)為1。為了保證對象的存在,可以調(diào)用retain函數(shù)保持對象,retain會使其引用計數(shù)加1,如果不需要這個對象可以調(diào)用release函數(shù),release使其引用計數(shù)減1。當對象的引用計數(shù)為0的時候,引擎就知道不再需要這個對象了,就會釋放對象內(nèi)存。

class Ref:
    def retain():pass 增加引用
    def release():pass 減少引用
    def autorelease():pass 交給自動釋放池
    def getRefreenceCount():pass
    self._referenceCount = 0
    #friend class AutoreleasePool

在使用Node節(jié)點對象時候,addChild函數(shù)可以保持Node節(jié)點對象,使引用計數(shù)加1。通過removeChild函數(shù)移除Node節(jié)點對象,使引用計數(shù)減1。

2.1 Ref::retain()

retain()對其引用計數(shù)加1

void Ref::retain()  
{  
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");  
    ++_referenceCount;  
}  
2.2 Ref::release()

release()先對引用計數(shù)-1,然后判斷引用計數(shù)是否為0,若為0則刪除對象

void Ref::release()  
{  
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");  
    對其引用計數(shù)值進行-1  
    --_referenceCount;  
    引用計數(shù)為0,刪除對象  
    if (_referenceCount == 0)  
    {  
        #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
        auto poolManager = PoolManager::getInstance();  
        if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))  
        {  
            CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");  
        }  
        #endif  
        從保存Ref*的list中刪除  
        #if CC_REF_LEAK_DETECTION  
            untrackRef(this);  
        #endif  
        刪除該對象  
        delete this;  
    }  
}  
2.3 Ref::autorelease()

autorelease()將節(jié)點添加到自動釋放池中,它由PoolManager進行管理。
調(diào)用內(nèi)存管理PoolManager的單例對象中存儲的自動釋放池對象的addObject函數(shù)加入當前的Ref實例對象的指針
cocos2dx為了不用顯式調(diào)用AutoRelease,一般讓程序員通過create來獲取對象,而create里幫我們調(diào)用好了AutoRelease。autorelease主要應(yīng)用在ui元素上。

Ref* Ref::autorelease()  
{  
    
    PoolManager::getInstance()->getCurrentPool()->addObject(this);  
    return this;  
}  

3. CCAutoreleasePool

這個類主要含有下面幾個方法

void addObject(Ref *object); 將Ref添加到自動釋放池  
void clear(); 清除自動釋放池所有對象  
bool contains(Ref* object) const; 檢測自動釋放池中是否有Object對象  
void dump(); 打印刪除的對象的地址  
3.1 構(gòu)造函數(shù)

構(gòu)造函數(shù)里可以看出,每一個autoreleasePool對象都有一個管理obj的隊列managedObjectArray用于存放obj,并且所有Pool對象都是放在poolManage的單例對象的堆中

AutoreleasePool::AutoreleasePool()  
    : _name("")
    #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
        , _isClearing(false)
    #endif  
    {   容器擴容,申請保存ref的vector的size增加150  
        _managedObjectArray.reserve(150);  
        把AutoreleasePool添加到管理類生明的vector中  
        PoolManager::getInstance()->push(this);  
    } 
3.2 析構(gòu)函數(shù)

析構(gòu)函數(shù)是先清除自動釋放池所有obj對象,然后再將自己從poolManage的堆中彈出

AutoreleasePool::~AutoreleasePool()  
{  
    CCLOGINFO("deallocing AutoreleasePool: %p", this);  
    clear();  
    PoolManager::getInstance()->pop();  
}  
3.3 addObject

把object添加到managedObjectArray自動釋放池

void AutoreleasePool::addObject(Ref* object)  
{  
    _managedObjectArray.push_back(object);  
} 
3.4 clear

clear就是將managedObjectArray里的所有obj都執(zhí)行release

void AutoreleasePool::clear()  
{  
    #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
        _isClearing = true;
    #endif  
    std::vector<Ref*> releasings;  
    releasings.swap(_managedObjectArray);  
   //遍歷自動釋放池managedObjectArray里存放的所有的Ref  
    for (const auto &obj : releasings)  
    {  
        //調(diào)用obj的release(),對obj的引用計數(shù)-1(如果對象引用計數(shù)為0則刪除)  
        obj->release();  
    }  
    #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
        _isClearing = false;
    #endif  
}  

4. PoolManager

  1. PoolManager類對象是一個單例對象singleInstance ,整個工程中只有一個
  2. PoolManager類維護著一個堆releasePoolStack,用來存放工程中所有的AutoreleasePool對象;
  3. 名字叫"cocos2d autorelease pool"的自動釋放池對象保存工程中大部分渲染的節(jié)點以及我們通過create()創(chuàng)建的對象,這個AutoreleasePool對象是工程剛啟動時就生成的;
4.1 順便通過PoolManager來重新學(xué)一下
static PoolManager* s_singleInstance; //poolmanager的實例,再強調(diào)一遍這是個單例對象  

PoolManager* PoolManager::s_singleInstance = nullptr; 

PoolManager* PoolManager::getInstance()  
{  
    //判斷是否存在singleInstance   
    if (s_singleInstance == nullptr)  
    {  
        //如果singleInstance 為nullptr,new一個PoolManager單例  
        s_singleInstance = new (std::nothrow) PoolManager();  
        //new一個AutoreleasePool并取名字為"cocos2d autorelease pool"  
        new AutoreleasePool("cocos2d autorelease pool");  
    }  
    return s_singleInstance;  
}  

void PoolManager::destroyInstance()  
{  
    delete s_singleInstance;  
    s_singleInstance = nullptr;  
}  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容