cocos2d-x 内存管理

cocos2d-x 是以C++ 为主要实现语言, 在C++11版本加入了智能指针.

c++内存分配区域

1。堆 (动态变量) 以new分配, delete释放。

2。栈 (局部变量) 一般在函数内部创建,当局部变量创建时进行压栈操作。 作用域从定义到函数结束,函数结束时按照顺序进行出栈操作。

3。全局区(全局变量/静态变量)

4。常量区(常量)

5。代码段


堆、栈各自的特点:

堆, 使用者可以控制数据的生命周期,即使数据是在某个函数中创建的,离开该函数,数据以及数据所占用的内存保持不变。如果没有控制好生命周期就会出现:内存泄露、野指针、重复释放等问题。

栈,使用者无需也不能控制变量的生命周期,当函数结束的时候,局部变量生命周期结束。


c++11 加入智能指针,其方式就是每一个动态变量与一个局部变量进行结合,在创建一个动态变量的时候将其与一个局部变量进行绑定。以局部变量的生命周期控制动态变量的生命周期,当局部变量离开作用域被自动释放的时候,同时释放动态变量所对应的内存空间。以对象的的方式进行内存管理,并在适当的时候进行内存释放(析构).

shared_ptr指针:使用方式

std::shared_ptr<int> num(new int(5));
log("shared_ptr %d", *num);

shared_ptr可以与其他智能指针共享内存,采用引用计数的方式进行管理,只有当所有内存都执行reset函数,或者离开作用域的时候,才会真正释放内存。shared_ptr为了保证线程安全,加入互斥锁。互斥锁对性能有影响。创建shared_ptr需要显示声明智能指针,编写代码时候注意事项比较多。

std::shared_ptr<Node*> node (new Node());
weak_ptr<Node*> refNode = node;


weak_ptr指针:使用方式

std::weak_ptr<int> num1 = num;

log("weak_ptr %d", *num1.lock());

可以指向shared_ptr指针的内存,但不拥有该内存。通过lock成员访问该内存,当内存无效的时候返回nullptr, 常用于检验shared_ptr的有效性。使用指针的时候最好使用

weak_ptr来间接访问内存。


unique_ptr指针:使用方式

std::unique_ptr<int> num2 (new int(10));
log("unique_ptr %d", *num2);

unique_ptr指针不能与其他指针共享内存,如:

std::unique_ptr<int> ptr1 (new int(5));

std::unique_ptr<int> ptr2 = ptr1; //编译时会报错

可以通过move函数转移控制权,std::unique_ptr<int> ptr2 = move(ptr1);

一但转移成功原先的unique_ptr指针就失去了对内存的使用权, 在使用就会报错。

函数结束,或者调用reset方法,释放内存。



目前内存回收大致分为垃圾回收和引用计数

cocos2d-x 是采用引用计数方式回收内存

AutoreleasePool 类 用于对Ref元素进行批量引用计数操作。

Ref::autorelease(); //将Ref对象,加入当前AutoreleasePool实例对象。

AutoreleasePool::addObject(Ref* object); //将Ref对象加入Autoreleasepool对象。
AutoreleasePool::clear(); 将已加入AutoreleasePool对象的Ref对象,进行release操作,
并将AutoreleasePool对象有关联的Ref对象全部与该AutoreleasePool取消关联。


Ref类是cocos2d-x所有类的基类,Ref类的主要功能是使用引用计数的方法,控制元素的内存管理。和将Ref对象加入AutoreleasePool

unsigned int  _referenceCount; //引用计数变量

void retain();  //对引用计数加1

void release(); //对引用计数减1

unsigned int getReferenceCount() const; //取得当前元素引用计数

在Ref 元素被创建的时候,_referenceCount初始化为1。执行一次retain()引用计数加1,执行一次release()引用计数减1,当引用计数为0时候,delete()。


在只有引用计数的时候怎么管理UI元素

auto node = new Node(); //引用计数为1

addChild(node); //引用计数为2


node->removeFromParent(); //引用计数为1

node->release(); //引用计数为0

如果忘记调用release方法就会内存泄露。


autorelease声明一个指针为”智能指针”

回想前面讲述的智能指针,如果将一个动态分配的内存关联到一个自动变量,则当这个自动变量的生命周期结束的时候将会释放这块堆内存,从而使程序员不必担心其内存释放。我们是否可以借鉴类似的机制来避免手动释放UI元素呢?

Cocos2d-x使用autorelease来声明一个对象指针为”智能指针”,但是这些”智能指针”并不单独关联到某个自动变量,而是全部被加入到一个AutoreleasePool中。在每一帧结束的时候对加入到AutoreleasePool中的对象进行清理,也即是说在Cocos2d-x中,一个“智能指针”的生命周期是从创建开始到当前帧结束。

Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}

如上的代码,Cocos2d-x通过autorelease方法将一个对象加入到AutoreleasePool中。

void DisplayLinkDirector::mainLoop()
{
if (! _invalid)
{
drawScene();

// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}

如上的代码,Cocos2d-x在每一帧结束的时候清理AutoreleasePool中的对象。

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
_managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}

实际的实现机制是AutoreleasePool对池中每个对象执行一次release操作,假设该对象的引用计数为1,表示其从未被使用,则执行release之后引用计数为0,将会被释放。例如创建一个不被使用的Node:

auto node=new Node(); //引用计数为1
node->autorelease(); //加入”智能指针池”

可以预期,在该帧结束的时候node对象将会被自动释放。如果该对象被使用,则:

auto node=new Node(); //引用计数为1
node->autorelease(); //加入”智能指针池”
addChild(node); //引用计数为2

则在该帧结束的时候,AutoreleasePool对其执行一次release操作之后引用计数为1,该对象继承存在。当下次该节点被移除的时候引用计数为0,就会被自动释放。通过这样,就实现了Ref对象的自动内存管理。

然而,不管是C++11中的智能指针,还是Cocos2d-x中变体的“智能指针”,都需要程序员手动声明其是“智能”的:

shared_ptr np1(new int()); //C++11声明智能指针
auto node=(new Node())->autorelease(); //Cocos2d-x中声明”智能指针”

为了简化这种声明,Cocos2d-x使用静态的create方法()来返回一个”智能指针”对象,Cocos2d-x中大部分的类都可以通过create来返回一个“智能指针”,例如Node,Action等,同时我们自定义的UI元素也应该遵循这样的风格,来简化其声明:

Node * Node::create(void)
{
Node * ret = new Node();
if (ret && ret->init()){
ret->autorelease();
}
else{
CC_SAFE_DELETE(ret);
}
return ret;
}

AutoreleasePool队列

对于有些游戏对象而言,”一帧”的生命周期显然有些过长,假设一帧会调用100个方法,每个方法创建10个“智能指针”对象,并且这些对象只在每个方法作用域内被使用,则在该帧末尾的时候内存当中的最大峰值为1000个游戏对象所占用的内存,这样游戏的平均内存占用将会大大增加,而实际上每帧平均只需要占用10个对象的内存,假设这些方法是顺序执行的。

默认AutoreleasePool一帧被清理一次主要是用来清理UI元素的,由于UI元素大部分都是添加到UI树中,会一直占用内存的,这种情况下每帧清理并不会对内存占用有多大影响。

显然,对于自定义数据对象,我们需要能够自定义AutoreleasePool的生命周期。Cocos2d-x通过实现一个AutoreleasePool的队列来实现“智能指针”生命周期的自定义[引用5],并由PoolManager来管理这个AutoreleasePool队列:

class CC_DLL PoolManager
{
public:
static PoolManager* getInstance();
static void destroyInstance();

AutoreleasePool *getCurrentPool() const;
bool isObjectInPools(Ref* obj) const;

friend class AutoreleasePool;

private:
PoolManager();
~PoolManager();

void push(AutoreleasePool *pool);
void pop();

static PoolManager* s_singleInstance;

std::deque _releasePoolStack;
AutoreleasePool *_curReleasePool;
};

PoolManager初始和默认至少有一个AutoreleasePool,它主要用来存储前面讲述的Cocos2d-x中的UI元素对象。我们可以创建自己的AutoreleasePool对象,将其压入到队列尾端。但是如果我们使用new运算符来创建AutoreleasePool对象,则又需要手动释放,为了达到和智能指针使用自动变量来管理内存的效果,Cocos2d-x对AutoreleasePool的构造和析构函数进行了特殊处理,以使我们可以通过自动变量来管理内存释放:

AutoreleasePool::AutoreleasePool()
: _name(“”)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}

AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}

AutoreleasePool::~AutoreleasePool()
{
CCLOGINFO(“deallocing AutoreleasePool: %p”, this);
clear();

PoolManager::getInstance()->pop();
}

AutoreleasePool在构造函数中将自身指针添加到PoolManager的AutoreleasePool队列中,并在析构函数中从队列中移除自己,由于前面讲述的Ref::autorelease()始终将自己添加到“当前AutoreleasePool”中,只要当前AutoreleasePool始终为队列尾端的元素,声明一个AutoreleasePool对象就可以影响之后的对象,直到该AutoreleasePool对象被移除队列。这样在程序中我们就可以这么使用:

Class MyClass : public Ref
{
static MyClass* create(){
auto ref=new MyClass();
return ref->autorelease();
}
}

void customAutoreleasePool()
{
AutoreleasePool pool;
auto ref1=MyClass::create();
auto ref2=MyClass::create();
}

在该方法开始执行时,声明一个AutoreleasePool类型的自动变量pool,其构造函数会将自身加入的PoolManager的AutoreleasePool队列尾端,接下来ref1和ref2都会被加入到pool池中,当该方法结束时,pool自动变量的生命周期结束,其析构函数将会释放对象,并从队列中移除自己。

这样我们就能够通过自定义AutoreleasePool的生命周期来控制Cocos2d-x中“智能指针”的生命周期。


Cocos2d-x有一套性能高效且实现精巧的内存管理机制,它本质上是一种“智能指针”的变体。它通过Ref::autorelease来声明一个“智能指针”,并通过将autorelease包装在create方法中,避免了程序员对“智能指针”的声明,默认在一帧结束的时候AutoreleasePool会清理所有的“智能指针对象”,并且我们可以自定义AutoreleasePool的作用域。

结合Cocos2d-x内存管理机制和特点,本节最后总结一些使用Cocos2d-x内存管理的注意事项:
1. Ref的引用计数并不是线程安全的,在多线程中我们需要处理互斥锁来保证线程安全。在Objective-C中由于AutoreleasePool是语言级别系统实现的,每个线程都有自己的AutoreleasePool队列。
2. 对于自定义Node的子类,为该类添加create方法,该方法返回一个autorelease对象。
3. 对于自动义数据类型,如果需要动态分配内存的,继承自Ref,并添加create静态方法返回autorelease对象。
4. 只在一个方法内部使用的Ref对象,使用自定义的AutoreleasePool来即时清理内存占用。
5. 不要动态分配AutoreleasePool对象,始终使用自动变量。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值