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