轉(zhuǎn)載自:http://blog.csdn.net/ring0hx/article/details/7946397
cocos2dx的內(nèi)存管理移植自O(shè)bjective-C, 對(duì)于沒(méi)有接觸過(guò)OC的C++開(kāi)發(fā)人員來(lái)說(shuō)是挺迷惑的。不深入理解內(nèi)存管理是無(wú)法寫(xiě)出好的C++程序的,我用OC和cocos2dx也有一段時(shí)間了,在此總結(jié)一下,希望對(duì)想用cocos2dx開(kāi)發(fā)游戲的朋友有所幫助。
C++的動(dòng)態(tài)內(nèi)存管理一般建議遵循誰(shuí)申請(qǐng)誰(shuí)釋放的原則,即誰(shuí)通過(guò)new操作符創(chuàng)建了對(duì)象,誰(shuí)就負(fù)責(zé)通過(guò)delete來(lái)釋放對(duì)象。如果對(duì)象的生命周期在一個(gè)函數(shù)內(nèi),這很容易做到,在函數(shù)返回前delete就行了。但一般我們?cè)诤瘮?shù)中new出來(lái)的對(duì)象的生命周期都會(huì)超出該函數(shù)體(例如作為函數(shù)的返回值),否則我們可以直接在棧上創(chuàng)建對(duì)象,不需要使用動(dòng)態(tài)內(nèi)存,也省去了內(nèi)存管理的麻煩(當(dāng)然大對(duì)象是不適合在棧上分配的)。如果對(duì)象的生命周期超出了創(chuàng)建對(duì)象的函數(shù),我們就很難再遵循誰(shuí)申請(qǐng)誰(shuí)釋放的原則了,我們必須在函數(shù)外的某個(gè)合適的地方釋放對(duì)象,如果這個(gè)對(duì)象同時(shí)被多個(gè)對(duì)象引用,問(wèn)題就很復(fù)雜了,必須保證所有該對(duì)象的引用者都不再需要它了才可以釋放,在實(shí)際使用中這點(diǎn)是很難保證的。于是,各種內(nèi)存管理技術(shù)應(yīng)運(yùn)而生了:垃圾回收器,智能指針,引用計(jì)數(shù)...... cocos2dx移植于Objective-C,因此和OC一樣使用了比較原始的引用計(jì)數(shù)的方法來(lái)管理內(nèi)存。
cocos2dx通過(guò)CCObject和CCPoolManager來(lái)實(shí)現(xiàn)內(nèi)存管理。所有使用cocos2dx引用計(jì)數(shù)機(jī)制的類(lèi)都必須派生自CCObject。CCObject有一個(gè)計(jì)數(shù)器成員變量m_uReference,當(dāng)CCObject被構(gòu)造時(shí)m_uReference=1,表示該對(duì)象被引用1次。CCObject的retain方法可以使計(jì)數(shù)器加1,release方法可以使計(jì)數(shù)器減1。當(dāng)計(jì)數(shù)器減到0時(shí)release方法會(huì)通過(guò)delete this來(lái)銷(xiāo)毀自己。
手動(dòng)內(nèi)存管理
CCArray *array = CCArray::array();
CCObject *obj = new CCObject();// m_uReference=1
array->addObject(obj); // CCArray的addObject方法會(huì)自動(dòng)調(diào)用obj的retain方法,使引用計(jì)數(shù)加1,表示擁有obj,此時(shí)m_uReference=2
obj->release(); // 這里的release和new配對(duì),obj引用計(jì)數(shù)減1,但是并不會(huì)釋放obj, 此時(shí)m_uReference=1;
obj->doSomething(); // 在release之后我們依然可以正常使用obj,它并沒(méi)有被釋放
array->removeObject(obj); //當(dāng)我們把obj從CCArray中移除時(shí),CCArray會(huì)自動(dòng)調(diào)用obj的release,此時(shí)m_uReference=0, obj被銷(xiāo)毀
obj->doSomething(); // 錯(cuò)誤,obj已經(jīng)被銷(xiāo)毀 對(duì)于手動(dòng)內(nèi)存管理,我們需遵循new/release,retain/release配對(duì)使用的原則,誰(shuí)new,誰(shuí)release;誰(shuí)retain,誰(shuí)release。new出來(lái)的對(duì)象如果是要加入到cocos2dx集合中,添加完后一定不要忘記release,集合類(lèi)已經(jīng)為你retain了對(duì)象,你還是要為你的new配對(duì)release一次,否則當(dāng)這個(gè)對(duì)象從集合中移除時(shí)不會(huì)被正確銷(xiāo)毀。
自動(dòng)內(nèi)存管理
手動(dòng)內(nèi)存管理似乎比new/delete更麻煩,而且并沒(méi)有解決一開(kāi)始我們提到的函數(shù)內(nèi)創(chuàng)建的對(duì)象的生命周期超出函數(shù)怎么辦的問(wèn)題。new和release需配對(duì)使用,那在函數(shù)內(nèi)創(chuàng)建的對(duì)象返回前我們需要調(diào)用一次release,在這之前如果我們沒(méi)有把對(duì)象加入到什么集合中,對(duì)象就被銷(xiāo)毀了,和使用new/delete是一樣的。自動(dòng)內(nèi)存管理就可以解決這個(gè)問(wèn)題。CCObject有一個(gè)autorelease方法,如果一個(gè)對(duì)象在用new關(guān)鍵字創(chuàng)建之后調(diào)用了autorelease,我們就不必關(guān)心它的釋放問(wèn)題。CCPoolManager會(huì)在游戲的每一幀結(jié)束后自動(dòng)釋放這些autorelease的對(duì)象。CCPoolManager其實(shí)依然是通過(guò)引用計(jì)數(shù)來(lái)管理對(duì)象生命周期的,它里面有一個(gè)CCAutoreleasePool,我們調(diào)用CCObject的autorelease就是把自己加入到CCAutoreleasePool的對(duì)象數(shù)組里面。當(dāng)每一幀結(jié)束的時(shí)候,CCPoolManager會(huì)將對(duì)象從數(shù)組中移除,如果這時(shí)候?qū)ο蟮囊糜?jì)數(shù)為0,對(duì)象就自然被釋放了。對(duì)于用new關(guān)鍵字創(chuàng)建之后調(diào)用了autorelease的對(duì)象,不需要再release一次。
cocos2dx中的大部分對(duì)象都可以通過(guò)靜態(tài)工廠方法來(lái)創(chuàng)建出這種會(huì)自動(dòng)釋放的對(duì)象,這是cocos2dx的一條規(guī)則,我們自己實(shí)現(xiàn)的類(lèi)最好也遵循這樣的規(guī)則,以免引起其他開(kāi)發(fā)人員誤會(huì)。如果一個(gè)對(duì)象是通過(guò)類(lèi)的靜態(tài)方法創(chuàng)建而不是new出來(lái)的,我們就不需要release它。
其實(shí)這里的自動(dòng)并沒(méi)有我們想得那么好,對(duì)于像C#,Java這種托管語(yǔ)言,虛擬機(jī)為你完成了所有內(nèi)存管理工作,程序員完全從內(nèi)存分配和釋放中解脫了出來(lái)。cocos2dx的autorelease只不過(guò)每幀結(jié)束后自動(dòng)在為我們釋放了一次對(duì)象,如果我們希望創(chuàng)建的對(duì)象在下一幀仍然可以使用,我們需要顯式地retain一下這個(gè)對(duì)象或者把對(duì)象加入到集合中(集合會(huì)幫我們r(jià)etain一次)。既然retain了,我們還是不能忘記在適當(dāng)?shù)牡胤絩elease。比較常見(jiàn)的用法是創(chuàng)建一個(gè)autorelease對(duì)象作為類(lèi)成員變量,我們?cè)谕ㄟ^(guò)靜態(tài)方法得到實(shí)例的指針后除了賦值給類(lèi)成員,還要retain一次,然后在類(lèi)的析構(gòu)函數(shù)中release一次。如果沒(méi)有retain,以后使用該成員的時(shí)候就會(huì)因?yàn)閷?duì)象被銷(xiāo)毀而發(fā)生內(nèi)存訪問(wèn)錯(cuò)誤,這是新手很容易遇到的陷阱。