1. 程式人生 > >[當我在研究Cocos-2dx的源代碼時,我在想什麽]-Ref類,一切的起源

[當我在研究Cocos-2dx的源代碼時,我在想什麽]-Ref類,一切的起源

ane isn leaks style 內存泄漏 列表 incr ons public

【名詞解釋】
引用計數:引用計數是現代內存管理中常常使用到的一個概念。它的基本思想是通過計數方式實現多個不同對象同一時候引用一個共享對象,詳細地講,當創建一個對象的實例並在堆上分配內存時,對象的引用計數為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);
#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






[當我在研究Cocos-2dx的源代碼時,我在想什麽]-Ref類,一切的起源