【名词解释】
引用计数:引用计数是现代内存管理中经常使用到的一个概念,它的基本思想是通过计数方式实现多个不同对象同时引用一个共享对象,具体地讲,当创建一个对象的实例并在堆上分配内存时,对象的引用计数为1,在其他对象中需要持有这个共享对象时,需要把共享对象的引用计数加1,当其他对象不再持有该共享对象时,共享对象的引用计数减1,当共享对象的引用计数变成0时,对象的内存会被立即释放。(部分截取自维基百科)。
比较著名的使用引用计数的有COM和Objective-C,在COM的IUnknow接口中定义了三个函数:QueryInterface,AddRef和Release,它们分别用于获取接口对象、给接口对象增加计数,给接口对象减少计数,当内部计数变为0时,自动销毁接口对象。在Objective-C中,则定义了retain,release和autorelease函数,分别用于增加计数、减少计数以及将一个对象交给自动释放池对象AutoreleasePool进行管理,由AutoreleasePool对象负责调用release函数。
【Ref类的实现】
由于Cocos2d-x是在Cocos2d-iPhone的基础上发展而来,所以沿用了很多Objective-C的思想,Ref类的实现就是如此。
Ref类实现了引用计数的功能,它是引擎代码中绝大多数其他类的父类,定义在CCRef.h中,实现在CCRef.cpp中。其实在CCRef.h文件中不止定义了Ref类,还定义了Clonable类、一系列的宏定义和类型定义,不过我们暂且将精力放在Ref类的解读上。Ref类使用私有成员变量_referenceCount保存计数值,并通过retain,release和autorelease函数实现增减计数值。
<span style="font-family:Microsoft YaHei;font-size:18px;">class CC_DLL Ref { public: /** * Retains the ownership. * * This increases the Ref's reference count. * * @see release, autorelease * @js NA */ void retain(); /** * Releases the ownership immediately. * * This decrements the Ref's reference count. * * If the reference count reaches 0 after the descrement, this Ref is * destructed. * * @see retain, autorelease * @js NA */ void release(); /** * Releases the ownership sometime soon automatically. * * This descrements the Ref's reference count at the end of current * autorelease pool block. * * If the reference count reaches 0 after the descrement, this Ref is * destructed. * * @returns The Ref itself. * * @see AutoreleasePool, retain, release * @js NA * @lua NA */ Ref* autorelease(); /** * Returns the Ref's current reference count. * * @returns The Ref's reference count. * @js NA */ unsigned int getReferenceCount() const; protected: /** * Constructor * * The Ref's reference count is 1 after construction. * @js NA */ Ref(); public: /** * @js NA * @lua NA */ virtual ~Ref(); protected: /// count of references unsigned int _referenceCount; friend class AutoreleasePool; };</span>
Ref将构造函数声明为保护类型,防止直接生成Ref对象,在构造函数的成员初始化列表中将引用计数值_referenceCount初始化为1。retain函数将_referenceCount加1,release函数则减1,autorelease函数则将对象托管给AutoreleasePool对象进行管理,具体实现代码如下:
<span style="font-family:Microsoft YaHei;font-size:18px;">NS_CC_BEGIN Ref::Ref() : _referenceCount(1) // when the Ref is created, the reference count of it is 1 { } Ref::~Ref() { } void Ref::retain() { CCASSERT(_referenceCount > 0, "reference count should greater than 0"); ++_referenceCount; } void Ref::release() { CCASSERT(_referenceCount > 0, "reference count should greater than 0"); --_referenceCount; if (_referenceCount == 0) { delete this; } } Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; } unsigned int Ref::getReferenceCount() const { return _referenceCount; } NS_CC_END</span>
【Clonable类的定义】
Clonable类定义了复制Ref类对象的规约,类似于Java语言中接口java.lang.Clonable的作用,3.2版本建议使用clone函数,copy函数已属于废弃函数。
<span style="font-family:Microsoft YaHei;font-size:18px;">/** Interface that defines how to clone an Ref */ class CC_DLL Clonable { public: /** returns a copy of the Ref */ virtual Clonable* clone() const = 0; /** * @js NA * @lua NA */ virtual ~Clonable() {}; /** returns a copy of the Ref. * @deprecated Use clone() instead */ CC_DEPRECATED_ATTRIBUTE Ref* copy() const { // use "clone" instead CC_ASSERT(false); return nullptr; } };</span>
【回调函数的定义】
定义了动作、菜单和调度器的回调函数:
<span style="font-family:Microsoft YaHei;font-size:18px;">typedef void (Ref::*SEL_CallFunc)(); typedef void (Ref::*SEL_CallFuncN)(Node*); typedef void (Ref::*SEL_CallFuncND)(Node*, void*); typedef void (Ref::*SEL_CallFuncO)(Ref*); typedef void (Ref::*SEL_MenuHandler)(Ref*); typedef void (Ref::*SEL_SCHEDULE)(float); #define callfunc_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFunc>(&_SELECTOR) #define callfuncN_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncN>(&_SELECTOR) #define callfuncND_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncND>(&_SELECTOR) #define callfuncO_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncO>(&_SELECTOR) #define menu_selector(_SELECTOR) static_cast<cocos2d::SEL_MenuHandler>(&_SELECTOR) #define schedule_selector(_SELECTOR) static_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)</span>
上面回调函数的定义分为两步:类型定义和宏定义。我们以SEL_CallFuncO为例进行说明,首先通过typedef类型定义了一个成员函数指针SEL_CallFuncO,SEL_CallFuncO是Ref类的成员,同时接收Ref类型的指针形参:
typedef void (Ref::*SEL_CallFuncO)(Ref*);
第二步是定义将指定函数转换为SEL_CallFuncO类型函数指针的宏,简化使用者操作:
#define callfuncO_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncO>(&_SELECTOR)
上面这个宏CallFuncO_selector的作用就是将_SELECTOR表示的函数,通过static_cast强制转换为SEL_CallFuncO类型的函数指针的定义。
【内存泄漏的检测】
在上面讲解Ref实现过程中,我们故意忽略了一些次要的代码,其中就包括内存泄漏检测,这部分代码是以宏:
<span style="font-family:Microsoft YaHei;font-size:18px;">#define CC_USE_MEM_LEAK_DETECTION 0</span>
作为开关的。内存泄漏检测代码主要包括Ref类静态成员函数:
<span style="font-family:Microsoft YaHei;font-size:18px;">class CC_DLL Ref { // Memory leak diagnostic data (only included when CC_USE_MEM_LEAK_DETECTION is defined and its value isn't zero) #if CC_USE_MEM_LEAK_DETECTION public: static void printLeaks(); #endif };</span>
定义在CCRef.cpp文件内的静态函数(静态函数与普通函数不同之处在于,它只在声明它的文件中可见,其他文件不可见,同时,其他文件中可以定义相同名字的函数,不会发生冲突)
#if CC_USE_MEM_LEAK_DETECTION static void trackRef(Ref* ref); static void untrackRef(Ref* ref); #endif
trackRef函数在Ref类对象创建的时候调用,untrackRef在Ref类对象销毁的时候调用,Ref对象实例保存在静态链表__refAllocationList中,实现代码如下所示:
#if CC_USE_MEM_LEAK_DETECTION static std::list<Ref*> __refAllocationList; void Ref::printLeaks() { // Dump Ref object memory leaks if (__refAllocationList.empty()) { log("[memory] All Ref objects successfully cleaned up (no leaks detected).\n"); } else { log("[memory] WARNING: %d Ref objects still active in memory.\n", (int)__refAllocationList.size()); for (const auto& ref : __refAllocationList) { CC_ASSERT(ref); const char* type = typeid(*ref).name(); log("[memory] LEAK: Ref object '%s' still active with reference count %d.\n", (type ? type : ""), ref->getReferenceCount()); } } } static void trackRef(Ref* ref) { CCASSERT(ref, "Invalid parameter, ref should not be null!"); // Create memory allocation record. __refAllocationList.push_back(ref); } static void untrackRef(Ref* ref) { auto iter = std::find(__refAllocationList.begin(), __refAllocationList.end(), ref); if (iter == __refAllocationList.end()) { log("[memory] CORRUPTION: Attempting to free (%s) with invalid ref tracking record.\n", typeid(*ref).name()); return; } __refAllocationList.erase(iter); } #endif // #if CC_USE_MEM_LEAK_DETECTION