cocos2d-x 3.6版本学习笔记-内存管理之智能指针

时间:2021-07-22 20:09:03

cocos对于集合类的UI对象,通过将其绑定对应的UI操作以控制其资源的释放,对于单个的非集合类元素,则只能手动retain和release来控制,这其实和直接new和delete无区别,都属比较难管理。为了安全地管理这些对象,cocos引入了智能指针:RefPtr<T>,这些指针可能保证资源始终可以被释放,即使使用过程中出现异常。

说到智能指针,首先需要了解一个概念:RAII(Resource Acquisition is Initialization)。在RAII机制下,动态资源的持有发生在一个对象的生命周期之内,在对象的构造函数中分配资源,在析构函数中释放内存。RAII实际上是一种代理模式,它巧妙地利用C++中,临时变量的内存由系统自动管理(stack内存)的特点,将动态变量的内存管理绑定到临时变量上,详情参考 C++之RAII惯用法

cocos2d-x中RefPtr<T>的实现代码如下(代码1):

template <typename T> class RefPtr
{
public:
    inline RefPtr():_ptr(nullptr){}
    inline RefPtr(RefPtr<T> && other){//右值引用
        _ptr = other._ptr;
        other._ptr = nullptr;
    }

    inline RefPtr(T * ptr):_ptr(const_cast<typename std::remove_const<T>::type*>(ptr))     // Const cast allows RefPtr<T> to reference objects marked const too.
    {
        CC_REF_PTR_SAFE_RETAIN(_ptr);
    }
    
    inline RefPtr(std::nullptr_t ptr):_ptr(nullptr){}
    
    inline RefPtr(const RefPtr<T> & other):_ptr(other._ptr){
        CC_REF_PTR_SAFE_RETAIN(_ptr);
    }
    
    inline ~RefPtr(){
        CC_REF_PTR_SAFE_RELEASE_NULL(_ptr);
    }
    
    // 这里省去一些重载操作符的函数
    inline RefPtr<t> & operator = (RefPtr<t> && other)
    {
        if (&other != this)
        {
            CC_REF_PTR_SAFE_RELEASE(_ptr);
            _ptr = other._ptr;
            other._ptr = nullptr;
        }
        
        return *this;
    }

    inline T * get() const { return reinterpret_cast<T*>(_ptr); }
    inline operator bool() const { return _ptr != nullptr; }
        
    inline void reset(){
        CC_REF_PTR_SAFE_RELEASE_NULL(_ptr);
    }
        
    inline void swap(RefPtr<T> & other){
        if (&other != this){
            Ref * tmp = _ptr;
            _ptr = other._ptr;
            other._ptr = tmp;
        }
    }
    
    inline void weakAssign(const RefPtr<T> & other){
        CC_REF_PTR_SAFE_RELEASE(_ptr);
        _ptr = other._ptr;
    }
    
private:
    Ref * _ptr;
};</t></t>
CC_REF_PTR_SAFE_RETAIN和CC_REF_PTR_SAFE_RELEASE是对对象retain和release的宏,定义如下(代码2):

#define CC_REF_PTR_SAFE_RETAIN(ptr)\
    \
    do\
    {\
        if (ptr)\
        {\
            const_cast<Ref*>(static_cast<const Ref*>(ptr))->retain();\
        }\
    \
    }   while (0);

#define CC_REF_PTR_SAFE_RELEASE(ptr)\
    \
    do\
    {\
        if (ptr)\
        {\
            const_cast<Ref*>(static_cast<const Ref*>(ptr))->release();\
        }\
    \
    }   while (0);

#define CC_REF_PTR_SAFE_RELEASE_NULL(ptr)\
    \
    do\
    {\
        if (ptr)\
        {\
            const_cast<Ref*>(static_cast<const Ref*>(ptr))->release();\
            ptr = nullptr;\
        }\
    \
    }   while (0);
可以看出,RefPtr<T>也是基于引用计数还管理内存的,所有的T必须是Ref类型,构造时传入的T会在编译时被const_cast转换以进行类型检查。

在构造函数里,RefPtr会对传入的非nullPtr对象引用计数加1,除非它是一个右值(简单点理解,可以取址的为左值,不可以取址的为右值)。

RefPtr重写的赋值运算符(上面只列了一个重载=号的函数),每赋值一次,引用次数加1。

弱引用执行了减1操作(weakAssign函数)。

出于性能的考虑,RefPtr并不是是线程安全,多线程时,需要添加互斥锁。


cocos主要提供了autoreleasePool和智能指针两种内存管理方式,对此,秦春林老师的建议是:

1)对自定义的Node的子类,为该类添加create()方法,并使该方法返回一个autorelease对象。

2)对自定义的数据类型,如果需要动态分配内存,继承自Ref,使用智能指针。

3)对只在一个方法内存使用的Ref对象,需要使用自动回收池的,用autorelease对象。

4)不要动态分配内存的,使用自动变量。

5)不要显式调用RefPtr的构造函数,始终使用隐式方式调用构造函数,因为显式的构造函数会导致同时执行构造函数和赋值操作符,造成不必要的临时智能指针变量的产生。