内存管理主要有两种方式,一种是引用计数,还有一种是垃圾回收机制,而cocos2d-x使用的是前者,接下来解释一下引用计数的原理。
其原理就是在有指针需要引用这个对象的时候将引用计数加1,不需要的时候要减1。当引用计数为0的时候释放内存。
来看看Node的父类,类Ref。这个类有构造函数、析构函数和四个成员函数,retain()、release()、autorelease()和getReferenceCount(),还有一个成员变量_referenceCount。其他的成员变量和引用计数无关,这里不做讨论。下面来解释这四个成员函数和一个成员变量。
_referenceCount,用来存储引用的次数。构造函数在创建类的时候将_referenceCount初始化为1。
retain(),这个函数用于将引用计数加1。如果需要引用这块内存的时候没有加1,则会在使用的时候,可能会遇到使用的这块内存被释放的问题。
release(),这个函数用于将引用计数减1,当引用计数为0的时候释放这快内存。如果retian后没有调用release,引用计数不会为0,内存不会被释放,造成内存泄漏。
autorelease(),这个函数将对象放进自动释放池,自动释放池在每次mainLoop()函数的最后会调用clear()将自动释放池里面所有对象的引用计数都减1。这个函数使用在需要延时调用release()函数的指针上(如这块内存地址需要返回,最常见在create函数里面)。
只要记住这样一条法则就可以了:创建对象后或者调用了retian()一定要调用release(),如果要延迟释放的时候调用autorelease()。
使用引用计数的时候要注意一点,循环引用。其代码如下
A.h
#include <stdio.h>
class B;
class A:public cocos2d::Ref {
public:
A();
~A();
void setB(B * b);
private:
B * _b;
};
A.cpp
#include "A.hpp"
#include "B.hpp"
A::A():_b(nullptr)
{
}
A::~A()
{
_b->release();
}
void A::setB(B *b)
{
CC_SAFE_RELEASE(_b);
_b = b;
CC_SAFE_RETAIN(_b);
}
B.h
#include <stdio.h>
#include "A.hpp"
class B:public cocos2d::Ref {
public:
B();
~B();
void setA(A * a);
private:
A * _a;
};
B.cpp
B::B():_a(nullptr)
{
}
B::~B()
{
_a->release();
}
void B::setA(A *a)
{
CC_SAFE_RELEASE(_a);
_a = a;
CC_SAFE_RETAIN(_a);
}
使用时的代码
A * a = new (std::nothrow)A();
B * b = new (std::nothrow)B();
a->setB(b);
b->setA(a);
a->release();
b->release();
在类A里面有个类型为B成员变量_b,在setB(B *b)函数中对_b调用了retain()函数,在析构函数中对_b调用了release()函数。在类B里面有个类型为A的成员变量_a,在setA(A *a)函数中对_a调用了retain()函数。现在创建类A的实例a,类B的实例b,并且a->setB(b);b->setA(a),最后分别对a、b调用一次release()。这样就会造成循环引用,因为a的引用计数为1,b的引用计数也为1,a和b都没有被释放,也不会调用析构函数里面的release()释放a和b。从而造成内存泄漏。这里提供一种方法,根据程序需要将其中一个set函数里面的retain()去掉,也就是弱引用(weak reference)。
另外,在cocos2d自定义的Vector和Map两个容器类里面,只要是有数据插入就会调用retain函数,有数据删除就会调用release函数。因此,在每次调用create工厂方法后都要将返回值放入到父节点里面或者调用retain方法,不然在下一次调用mainLoop函数的时候就会使用已经释放的内存。
网友评论