C++内存管理
C++显式堆内存管理
性能上有一定优势,但有如下缺点:
- 野指针:指针指向的内容已经被释放,但是其他指针还可能指向它。
- 重复释放:重复释放一个已经释放的内存单元,或者释放一个野指针,都会导致C++运行时错误。
- 内存泄露:不再被使用的内存单元如果不被释放,就会一直占用内存单元。
C++11智能指针
主要有如下三种:
- unique_ptr:不能与其他智能指针共享所指对象的内存。一旦转移成功,就不能使用原来的指针,否则会导致运行时错误。
- shared_ptr:共享同一堆分配对象的内存,它在实现上采用引用指针。只有引用计数为零时,才会真正释放占有的堆内存。
- weak_ptr:指向shared_ptr指针分配的对象内存,但不拥有该内存。我们可以使用其lock成员来访问其指向内存的一个shared_ptr对象,当其所指向的内存无效时,返回空值(nullptr)。weak_ptr指针通常可以用来验证shared_ptr指针的有效性。
为什么Cocos2d-x不使用智能指针
有如下理由:
- 智能指针有较大的性能损失。
- 它需要程序员显示地声明智能指针。
- 在需要引用的地方,一般应该使用weak_ptr指针,否则在Node被移除的时候还要手动减持shared_ptr指针的引用计数。
程序员每天都面对代码,他们需要更自然的内存管理方式,就像语言自身的特性一样,甚至几乎察觉不到背后的机制。
C++垃圾回收机制
- 基于引用计数:当对象被引用的次数变为零时,该对象即被视作垃圾而被回收。
- 基于跟踪处理:先产生跟踪对象的关系图,再进行垃圾回收。
不管采用哪种方式,垃圾回收机制都可以使内存管理变得更自然。
Cocos2d-x内存管理
引用计数
Cocos2d-x中的所有对象几乎都继承自Ref基类,Ref基类的主要职责就是对对象进行引用计数管理。
- 当一个对象使用new运算符分配内存时,引用计数为1。
- 调用
retain()
方法会增加其引用计数。
- 调用
release()
方法会减少其引用计数。并且在其引用计数为0时自动调用delete运算符删除对象并释放内存。
Ref的引用计数并不是线程安全的。在多线程中,我们需要通过处理互斥锁来保证线程的安全。
自动回收池(AutoreleasePool)
Cocos2d-x在每一帧结束的时候清理当前AutoreleasePool中的对象,因此可以使用autorelease()
方法来声明一个对象指针加入AutoreleasePool。
为了化简这种声明,Cocos2d-x使用静态的create()
方法来调用加入AutoreleasePool。同时,自定义的UI元素也应该遵循这样的风格。
AutoreleasePool队列
对于一些游戏对象而言,一帧的生命周期有些长。我们需要能够自定义AutoreleasePool的生命周长。AutoreleasePool在构造函数中将自身指针添加到PoolManager的AutoreleasePool队列中,并在析构函数中从队列中移除自己。通过在函数开头定义AutoreleasePool对象,就能控制Cocos2d-x中的autorelease对象的声明周期了。
不要动态分配AutoreleasePool对象,而始终使用自动变量。
Cocos2d-x中的智能指针
Cocos2d-x 3.1 引入了智能指针RefPtr<T>
,是基于RAII实现的。
构造函数
RefPtr需要依赖Ref的引用计数来管理内存,因此所有类型T必须是Ref类型。
- 对于使用左值作为构造函数的参数,会使得引用计数加1。
- 而使用右值作为构造函数的参数,不会使得引用计数加1。而是使用了移动复制构造函数,将其对应的内存占用转移过来。
赋值操作符
- 对于使用左值来赋值,会使得引用计数加1。
- 而使用右值来赋值,不会使得引用计数加1。而是使用了移动复制构造函数,将其对应的内存占用转移过来。不过,有一点不同的是会释放之前旧的资源的引用计数。
弱引用赋值
RefPtr通过提供一个weakAssign()
方法来实现弱引用。
但是在析构的时候,依然会release()
一次。
有什么用呢?示例如下:
void a()
{
RefPtr<Texture2D> l;
l.weakAssign(new Texture2D);
return;
}
在函数中并没有delete,但是依旧不会造成内存泄露。
析构函数:对_ptr进行safe delete。
重载了“*”和“->”操作符,使得其能够直接访问资源的地址。另外也可以通过get()
方法来访问资源的地址。
RefPtr也重载了bool()
操作符,使我们可以直接判断其有效性。
- Cocos2d-x自动回收池和智能指针该用谁?
作者的建议是,所有的UI元素都需要使用autorelease来管理,而游戏中的数据则使用智能智能RefPtr来管理。
- RefPtr的缺陷
- 可以从外部修改引用计数。
- 虽然RefPtr提供了一种弱引用,不对其进行引用计数增加,但仍然表现为一个强类型智能指针的行为,它仍然可以对其资源进行修改。