[当我在研究Cocos-2dx的源代码时,我在想什么]-Ref类,一切的起源
引用计数:引用计数是现代内存管理中常常使用到的一个概念。它的基本思想是通过计数方式实现多个不同对象同一时候引用一个共享对象,详细地讲,当创建一个对象的实例并在堆上分配内存时,对象的引用计数为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函数实现增减计数值。
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; };Ref将构造函数声明为保护类型,防止直接生成Ref对象。在构造函数的成员初始化列表中将引用计数值_referenceCount初始化为1。retain函数将_referenceCount加1,release函数则减1。autorelease函数则将对象托管给AutoreleasePool对象进行管理,详细实现代码例如以下:
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【Clonable类的定义】
Clonable类定义了复制Ref类对象的规约,类似于Java语言中接口java.lang.Clonable的作用,3.2版本号建议使用clone函数,copy函数已属于废弃函数。
/** 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; } };【回调函数的定义】
定义了动作、菜单和调度器的回调函数:
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)上面回调函数的定义分为两步:类型定义和宏定义。我们以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实现过程中,我们有益忽略了一些次要的代码,当中就包含内存泄漏检測。这部分代码是以宏:
#define CC_USE_MEM_LEAK_DETECTION 0
作为开关的。
内存泄漏检測代码主要包含Ref类静态成员函数:
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 };
定义在CCRef.cpp文件内的静态函数(静态函数与普通函数不同之处在于。它仅仅在声明它的文件里可见,其它文件不可见。同一时候,其它文件里能够定义同样名字的函数,不会发生冲突)
#if CC_USE_MEM_LEAK_DETECTION static void trackRef(Ref* ref); static void untrackRef(Ref* ref); #endiftrackRef函数在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