从智能指针联想到cocos2dx内存管理机制

1. 写在前面

C++在C11之前,都是使用new分配内存delete释放内存。这看起来是不是非常轻松?其实这会暴露出许多缺点

  1. 内存重复释放
  2. 野指针:指向的内存已经被释放了,但是指针还在使用
  3. 内存泄露:不再使用的内存没有释放,内存占用率过高

C11就引出了智能指针来解决以上的三个问题。
我已经有博客提到智能指针了,想多了解的朋友可以去看看:C++多线程下的shared_ptrC11新特性之智能指针
shared_ptr:多个智能指针可共享同一内存,使用引用计数
unique_ptr:不能和其他智能指针指向同一个内存,直接赋值给其他指针会报错,但是可以使用move来转移,转移后之前的智能指针就失效。
week_ptr:使用lock()函数,它可以检测weak_ptr访问的对象是否存在

既然智能指针看起来解放了大家去释放内存的步骤,那么为什么在cocos里面我们不使用智能指针来管理内存呢?

  1. 性能损失
  2. 仍要显示声明指针

2. Ref

cocos2dx虽然没有使用智能指针,但是它用了一个与智能指针非常相似的东西,也就是下面我们要说的Ref。
Ref的主要流程
当对象被创建时候,引用计数为1。为了保证对象的存在,可以调用retain函数保持对象,retain会使其引用计数加1,如果不需要这个对象可以调用release函数,release使其引用计数减1。当对象的引用计数为0的时候,引擎就知道不再需要这个对象了,就会释放对象内存。

class Ref:
    def retain():pass 增加引用
    def release():pass 减少引用
    def autorelease():pass 交给自动释放池
    def getRefreenceCount():pass
    self._referenceCount = 0
    #friend class AutoreleasePool

在使用Node节点对象时候,addChild函数可以保持Node节点对象,使引用计数加1。通过removeChild函数移除Node节点对象,使引用计数减1。

2.1 Ref::retain()

retain()对其引用计数加1

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

release()先对引用计数-1,然后判断引用计数是否为0,若为0则删除对象

void Ref::release()  
{  
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");  
    对其引用计数值进行-1  
    --_referenceCount;  
    引用计数为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()将节点添加到自动释放池中,它由PoolManager进行管理。
调用内存管理PoolManager的单例对象中存储的自动释放池对象的addObject函数加入当前的Ref实例对象的指针
cocos2dx为了不用显式调用AutoRelease,一般让程序员通过create来获取对象,而create里帮我们调用好了AutoRelease。autorelease主要应用在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 构造函数

构造函数里可以看出,每一个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 析构函数

析构函数是先清除自动释放池所有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都执行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)  
    {  
        //调用obj的release(),对obj的引用计数-1(如果对象引用计数为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"的自动释放池对象保存工程中大部分渲染的节点以及我们通过create()创建的对象,这个AutoreleasePool对象是工程刚启动时就生成的;
4.1 顺便通过PoolManager来重新学一下
static PoolManager* s_singleInstance; //poolmanager的实例,再强调一遍这是个单例对象  

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;  
}  
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容