Cocos2d-x的内存管理(1)
C++的内存管理机制
优点
在C++中,开发者可以使用new操作符在运行时从堆中为程序分配一块内存空间,并直接获取该内存的地址进行操作。这种直接访问内存地址的方式极大地提高了内存使用的灵活性。当这块内存不再需要时,可以使用delete操作符将其释放。
缺点
C++的手动内存管理方式极易出错,常见的问题如下:
- 野指针:当指针指向的内存已经被释放,但其他指针可能仍然指向该内存空间。由于这块内存可能已被重新分配给其他内容,贸然使用这些指针可能会导致不可预知的结果。
- 重复释放:如果两个指针同时指向同一块内存,对这两个指针都进行内存释放操作,或者对一个指针释放两次内存,都会引发C++运行时错误。
- 内存泄漏:若使用
new分配了一块内存空间,但忘记使用delete释放,这块内存将一直被占用。在游戏开发中,内存泄漏问题尤为严重。因为游戏需要频繁地创建和删除元素,涉及大量的内存操作,很容易出现忘记释放大块内存的情况。而所有设备的内存空间都是有限的,大量内存被占用可能会导致程序崩溃或退出。
C++11中的智能指针
优点
在C++中,根据内存分配方式,变量可分为三种:静态变量和常量(存储在数据区)、动态变量(存储在堆区)和局部变量(存储在栈区)。
上一节我们讨论了动态变量的优缺点。这里着重介绍局部变量:局部变量存储在栈中,创建时进行压栈操作,其作用域从定义处开始,到函数结束为止。当函数结束时,会按照顺序进行出栈操作。开发者无需手动控制局部变量的生命周期,这正是动态变量所欠缺的特性。
智能指针的设计思路是在创建动态变量时,将其与一个局部变量绑定。这样,智能指针既具备动态变量的优势(提高内存使用的灵活性),又拥有局部变量的优势(开发者无需控制其生命周期)。当局部变量离开作用域被自动释放时,动态变量对应的内存空间也会同时被释放。
分类
- unique_ptr指针
- 定义方式:
unique_ptr<int> up1(new int(5)); - 特性:
unique_ptr不能与其他智能指针共享内存。例如,若执行unique_ptr<int> up11 = up1;,编译时会报错。不过,可以通过std::move函数转移内存的所有权。一旦转移成功,原unique_ptr将失去对该内存的所有权,再次使用会导致错误。可以使用*up1访问内存块,当退出函数或执行变量的reset函数时,会自动释放内存。 - shared_ptr指针
- 定义方式:
shared_ptr<int> up2(new int(5)); - 特性:
shared_ptr可以与其他智能指针共享内存,采用引用计数的方式管理内存。只有当所有指向该内存的shared_ptr都执行reset函数,或者离开作用域时,才会真正释放内存。 - weak_ptr指针
- 定义方式:
weak_ptr<int> up3 = up2; - 特性:
weak_ptr可以指向shared_ptr所管理的内存,但不拥有该内存。可以通过其lock成员函数访问该内存,当该内存无效时,lock函数将返回nullptr,常用于验证shared_ptr指针的有效性。
缺点
shared_ptr为了保证线程安全,加入了互斥锁,这会对性能产生一定的影响。- 创建
shared_ptr时需要显式声明智能指针,使用指针时最好使用weak_ptr间接访问内存,编写代码时需要注意的事项较多。
垃圾回收机制
原理
垃圾回收机制主要基于以下两种方法:
- 引用计数:使用引用计数记录对象被引用的次数,当引用次数为0时,该对象被视为垃圾并被回收。
- 标记整理:将程序中正在使用的对象所引用的内存空间做标记,未做标记的内存进行整理回收。
需要注意的是,C++本身并不支持垃圾回收机制。
Cocos2d-x的内存管理机制
使用引用计数
无论是shared_ptr还是垃圾回收机制,都借鉴了引用计数的原理。对于学过Objective - C编程的同学来说,retain和release这两个函数应该耳熟能详。这两个函数的作用是修改引用计数,retain将引用计数加1,release将引用计数减1。
Cocos2d - X的引擎内部同样使用引用计数进行内存管理。在基类Ref中,定义了retain和release这两个函数来操作引用计数。当元素通过new创建时,其引用计数初始为1;执行一次retain操作,引用计数变为2;执行一次release操作,引用计数减为1。当引用计数为0时,会执行delete操作删除该元素。
使用“智能指针”
在Cocos2d - X中,可以对元素调用autorelease函数,将其加入AutoReleasePool。AutoReleasePool会在每一帧结束时,对池中的所有元素执行一次release函数。
例如,一个元素创建时引用计数为1,加入AutoReleasePool后,当AutoReleasePool在一帧结束时自动执行release操作,若引用计数变为0,则该元素将被删除。
为了简化代码,Cocos2d - X将new和autorelease函数封装成了一个create函数。元素直接调用create函数,即可完成创建并加入AutoReleasePool的操作。
自定义AutoReleasePool
用户可以根据自己的需求创建自定义的AutoReleasePool。AutoReleasePool由PoolManager管理,采用栈的方式存放,默认有一个AutoReleasePool,用户可以创建自己的AutoReleasePool。
创建AutoReleasePool需要分配内存,理论上需要使用new从堆区获取内存。但为了避免用户手动管理AutoReleasePool的内存,Cocos2d - X在AutoReleasePool的构造和析构函数中加入了内存分配、压栈和出栈的操作。开发者只需创建一个局部变量的AutoReleasePool,即可自动享受内存管理并控制其生命周期。
创建自定义的AutoReleasePool后,会进行压栈操作,此时当前的AutoReleasePool即为用户创建的这个。此后执行autorelease函数的元素都将加入该AutoReleasePool。当AutoReleasePool的作用域结束时,会调用其析构函数,对其中的元素执行release操作,并将AutoReleasePool出栈。