cocos2dx的内存管理移植自Objective-C, 对于没有接触过OC的C++开发者来说是挺迷惑的。不深入理解内存管理是无法写出好的C++程序的,我用OC和cocos2dx也有一段时间了,在此总结一下,希望对想用cocos2dx开发游戏的朋友有所帮助。





C++的动态内存管理一般建议遵循谁申请谁释放的原则,即谁通过new操作符创建了对象,谁就负责通过delete来释放对象。假设对象的生命周期在一个函数内,这非常easy做到,在函数返回前delete即可了。但一般我们在函数中new出来的对象的生命周期都会超出该函数体(比如作为函数的返回值),否则我们能够直接在栈上创建对象,不须要使用动态内存,也省去了内存管理的麻烦(当然大对象是不适合在栈上分配的)。假设对象的生命周期超出了创建对象的函数,我们就非常难再遵循谁申请谁释放的原则了,我们必须在函数外的某个合适的地方释放对象,假设这个对象同一时候被多个对象引用,问题就非常复杂了,必须保证全部该对象的引用者都不再须要它了才干够释放,在实际使用中这点是非常难保证的。于是,各种内存管理技术应运而生了:垃圾回收器,智能指针,引用计数...... cocos2dx移植于Objective-C,因此和OC一样使用了比較原始的引用计数的方法来管理内存。




cocos2dx通过CCObject和CCPoolManager来实现内存管理。全部使用cocos2dx引用计数机制的类都必须派生自CCObject。CCObject有一个计数器成员变量m_uReference,当CCObject被构造时m_uReference=1,表示该对象被引用1次。CCObject的retain方法能够使计数器加1,release方法能够使计数器减1。当计数器减到0时release方法会通过delete this来销毁自己。




手动内存管理


使用retain和release,我们能够手动管理内存, 通过new 创建的对象,使用release来释放。



[cpp] ​​view plain​ ​​​copy​



  1. CCObject *obj=new CCObject();  
  2. ...   
  3. obj->release();  

和new\delete需配对使用一样,new\release也要配对使用才可确保内存被释放。有人会说这个把delete换成release有意义吗?须要注意的是这个的release并不等同于delete,release仅仅是表示引用计数减1,并非真正销毁obj所占用的内存。仅仅有当obj的引用计数为0时内存才会被销毁。以下的代码就展示了release和delete不同:




[cpp] ​​view plain​ ​​​copy​



  1. CCArray *array = CCArray::array();  
  2. CCObject *obj = new CCObject();// m_uReference=1  
  3. array->addObject(obj); // CCArray的addObject方法会自己主动调用obj的retain方法,使引用计数加1,表示拥有obj,此时m_uReference=2  
  4. obj->release(); // 这里的release和new配对,obj引用计数减1,可是并不会释放obj, 此时m_uReference=1;  
  5. obj->doSomething(); // 在release之后我们依旧能够正常使用obj,它并没有被释放  
  6. array->removeObject(obj); //当我们把obj从CCArray中移除时,CCArray会自己主动调用obj的release,此时m_uReference=0, obj被销毁  
  7. obj->doSomething(); // 错误,obj已经被销毁  

对于手动内存管理,我们需遵循new/release,retain/release配对使用的原则,谁new,谁release;谁retain,谁release。new出来的对象假设是要加入到cocos2dx集合中,加入完后一定不要忘记release,集合类已经为你retain了对象,你还是要为你的new配对release一次,否则当这个对象从集合中移除时不会被正确销毁。



自己主动内存管理


手动内存管理似乎比new/delete更麻烦,并且并没有解决一開始我们提到的函数内创建的对象的生命周期超出函数怎么办的问题。new和release需配对使用,那在函数内创建的对象返回前我们须要调用一次release,在这之前假设我们没有把对象增加到什么集合中,对象就被销毁了,和使用new/delete是一样的。自己主动内存管理就能够解决问题。CCObject有一个autorelease方法,假设一个对象在用newkeyword创建之后调用了autorelease,我们就不必关心它的释放问题。CCPoolManager会在游戏的每一帧结束后自己主动释放这些autorelease的对象。CCPoolManager事实上依旧是通过引用计数来管理对象生命周期的,它里面有一个CCAutoreleasePool,我们调用CCObject的autorelease就是把自己增加到CCAutoreleasePool的对象数组里面。当每一帧结束的时候,CCPoolManager会将对象从数组中移除,假设这时候对象的引用计数为0,对象就自然被释放了。对于用newkeyword创建之后调用了autorelease的对象,不须要再release一次。




cocos2dx中的大部分对象都能够通过静态工厂方法来创建出这种会自己主动释放的对象,这是cocos2dx的一条规则,我们自己实现的类最好也遵循这种规则,以免引起其它开发者误会。假设一个对象是通过类的静态方法创建而不是new出来的,我们就不须要release它。




事实上这里的自己主动并没有我们想得那么好,对于像C#,Java这样的托管语言,虚拟机为你完毕了全部内存管理工作,程序猿全然从内存分配和释放中解脱了出来。cocos2dx的autorelease仅仅只是每帧结束后自己主动在为我们释放了一次对象,假设我们希望创建的对象在下一帧仍然能够使用,我们须要显式地retain一下这个对象或者把对象增加到集合中(集合会帮我们retain一次)。既然retain了,我们还是不能忘记在适当的地方release。比較常见的使用方法是创建一个autorelease对象作为类成员变量,我们在通过静态方法得到实例的指针后除了赋值给类成员,还要retain一次,然后在类的析构函数中release一次。假设没有retain,以后使用该成员的时候就会由于对象被销毁而发生内存訪问错误,这是新手非常easy遇到的陷阱。