[当我在研究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函数实现增减计数值。

<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
时间: 2024-10-24 12:08:49

[当我在研究Cocos-2dx的源码时,我在想什么]-Ref类,一切的起源的相关文章

cocos 屏幕适配源码分析及VisibleSize,VisibleOrigin

其实这个话题应该是从第一天接触cocos就会碰到的问题,我始终没能完全理解那些文章的意思,只是大概知道是怎么回事,仅此而已,智商捉急呀!!今天也是花了很长的时间去理解,现在总算有点眉目了,就把它记录下来,以后可以常回顾一下. 不废话了,进入正题.所谓的屏幕适配到底需要我们完成什么样的功能呢?这才是我们研究这个问题需要解决的东西,看了很多文章写屏幕适配,大神们总是在侃侃而谈,殊不知我们这些学渣理解能力确实有问题,所以经常一篇文章读下来,确实能理解里面讲的是什么,但是为什么要解决这个问题,为什么要这

AndroidPN的学习研究(二)源码结构分析

Server部分的主要包结构如下: 其中 org.androidpn.server.dao,org.androidpn.server.model和 org.androidpn.server.service为使用hibernate链接数据库并实现简单的用户登录认证,开发中可以用我们自己的认证模 块替换.剩下的包就是推送的主体实现.      接下来逐个包来看:     1.util包中的类用来加载resources中的配置文件,在配置文件中可指定监听端口和ssl证书目录等属性. 2.org.and

AndroidPN的学习研究(三)源码流程分析

在客户端中,我们在AndroidManifest.xml文件中找到程序入口,即如下的DemoAppActivity类, 在此类中主要代码如下 ServiceManager serviceManager = new ServiceManager(this); serviceManager.setNotificationIcon(R.drawable.notification); serviceManager.startService(); 一路跟进,NotificationService中有个Xm

underscore.js源码研究(8)

概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以就了结研究underscore源码这一心愿吧. underscore.js源码研究(1) underscore.js源码研究(2) underscore.js源码研究(3) underscore.js源码研究(4) underscore.js源码研究(5) underscore.js源码研究(6)

传智播客C/C++学员荣膺微软&amp;Cocos 2d-x黑客松最佳创新奖

?? 6月30日,历时32小时的微软开放技术Cocos 2d-x 编程黑客松在北京望京微软大厦成功落下帷幕,这是微软开放技术首次联合Cocos 2d-x 在中国举办黑客松.此次活动共有包括传智播客C/C++学院的学员组成的闪游队,以及Hurry队.繁星队等在内的18个团队,70人参加了这场开发盛事,参赛团队中,不乏经验丰富的一线手游团队. "太空大战"项目演示 微软开放技术Cocos 2d-x 编程黑客松,是微软开放技术.Cocos2d-x 开源引擎联合举办的一场属于游戏编程开发者的聚

cocos 2d-x游戏开发启示录(创世纪新篇)

cocos 2d-x可以在pc电脑window,mac操作系统上开发游戏,也可以在移动设备上开发游戏,比如Android,windowphone等上开发,集成开发环境是:visual studio,eclipse,游戏引擎包括:粒子场景,物理引擎,瓦片区域等等. cocos 2d-x支持三种脚本语言:如c++,JavaScript,lua,你至少要熟悉一门脚本语言. 下面以cocos 2d-x的lua脚本语言开发一款<黑人小心>的游戏.开发工具是cocos IDE 先看看效果图: 1.项目结构

[脚本无敌2]python获取cocos 2dx项目文件列表

在将cocos 2dx代码移植到android jni时会使用到,有需要的拿走就好,free-- # -*- coding: gbk -*- # function:获取cocos 2dx项目的cpp文件列表,在移植到android时使用 # input:项目路径 # output:txt格式的cpp文件列表 import time, os, sys import glob print 'input project path:' file_path_input = raw_input() # fi

(转)Cocos 2d-X Lua 游戏添加苹果内购(二) OC和Lua交互代码详解

这是第二篇 Cocos 2d-X Lua 游戏添加苹果内购(一) 图文详解准备流程 这是前面的第一篇,详细的说明了怎样添加内购项目以及填写银行信息提交以及沙盒测试员的添加使用以及需要我们注意的东西,结果,被移除首页了!前面第一篇的内容是这篇的基础,前面那些不弄好,下面的商品信息你是请求不到的,这点需要大家特别注意...有需要前面提到的内容的孩子可以点击链接进去自己看看!! 这篇就具体的总结我们Lua和OC交互的内容以及内购具体的代码以及结果的测试说明: 内购部分OC的代码实现 先自己总结一下整个

用eclipse 玩转cocos 2dx开发

开始研究cocos2dx,mark一下这个的配置步骤 1 下载eclipse      2 下载android sdk,配置sdk路径,添加环境变量 3 安装adt 4 下载android ndk,配置ndk路径 5 下载cocos2dx 开发包 注意 ndk8+ 不需要cygwin,可以省去N多下载的时间 下面以HelloCpp 为例 A 导入地址  D:\soft\cocos2d-x-2.1.5\samples\Cpp\HelloCpp\proj.androidsoft\cocos2d-x-