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对象,始终使用自动变量。