Skip to content

cocos2d x 3.3rc2 005 cocos中的智能指针

cheyiliu edited this page Jan 19, 2015 · 1 revision

继续看cocos的内存管理

  • 前面分析了cocos的独特的内存管理方式-引用计数和auto-release-pool,现在是否可以很happy的敲代码而不用关心内存问题了呢?
  • 答案自然是NO,auto-release-pool的实现依赖引用计数(pool在clear的时候,调用的是计数器的release),而引用计数也仅仅通过retain/release对儿来增持和减少其引用计数。 程序猿是人啊,他有可能忘记了new/delete中的delete,那么同样的retain/release中的release也当然可能被忘记调用了。
  • 咋办?且看cocos提供的另一重保障-智能指针RefPtr

注意事项

  • 在看下面代码的时候,尽量结合cocos的完整代码看,因为代码中有符号* 在markdown语法里面它可能把*当特殊字符处理了。导致在网页里面显示不出来,影响对源代码的理解。

智能指针RefPtr

什么是智能指针

  • 引用权威boost.org的描述吧

from http://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/smart_ptr.htm Smart pointers are objects which store pointers to dynamically allocated (heap) objects. They behave much like built-in C++ pointers except that they automatically delete the object pointed to at the appropriate time. Smart pointers are particularly useful in the face of exceptions as they ensure proper destruction of dynamically allocated objects. They can also be used to keep track of dynamically allocated objects shared by multiple owners.

Conceptually, smart pointers are seen as owning the object pointed to, and thus responsible for deletion of the object when it is no longer needed.

The smart pointer library provides six smart pointer class templates (scoped_ptr, scoped_array, shared_ptr, shared_array, weak_ptr, intrusive_ptr) They are examples of the "resource acquisition is initialization" idiom described in Bjarne Stroustrup's "The C++ Programming Language", 3rd edition, Section 14.4, Resource Management.

* 上面貌似很长,概括起来就两点有用
1. 定义:智能指针是持有分配在堆上的对象的指针,并能在适当的时候销毁该指针指向的对象的对象。
2. 思想:别看标准库提供了6种只能指针模版,但他们都是***RAII***思想的实现而已。
* RAII是什么?大家可参考下面ref中提到的*C++之RAII惯用法*。 简单理解就是:在对象的初始化(构造)时获得资源,在析构时释放资源,并利用栈变量生命期来使构造析构自然而然的发生,从而实现资源的自动管理。
* 提一提shared_ptr, 因为它和RefPtr有关系。shared_ptr的实现是基于计数器的。具体可查看ref中的'shared_ptr实现原理'
* 智能指针的用法举例, 请参考*ref*中的'sample of shared_ptr'



### 什么RefPtr
* 先看3.3final代码中的注释吧
  • Wrapper class which maintains a strong reference to a cocos2dx cocos2d::Ref* type object.
  • Similar in concept to a boost smart pointer.
  • Enables the use of the RAII idiom with Cocos2dx objects and helps automate some of the more
  • mundane tasks of pointer initialization and cleanup.
  • The class itself is modelled on C++ 11 std::shared_ptr, and trys to keep some of the methods
  • and functionality consistent with std::shared_ptr.
    
    
  • 概括下上面的注释
    • RefPtr,是沿用了RAII的思想的,以std::shared_ptr为原型的,维护着一个cocos2d::Ref* 类型的包装类。说白了RefPtr就是cocos自己实现的智能指针。

RefPtr源码分析

  • 注意查看相关注释

#ifndef __CC_REF_PTR_H__
#define __CC_REF_PTR_H__

#include "base/CCRef.h"
#include "base/ccMacros.h"
#include <type_traits>

NS_CC_BEGIN

/**
 * Utility/support macros. Defined to enable RefPtr<T> to contain types like 'const T' because we do not
 * regard retain()/release() as affecting mutability of state.
 */
#define CC_REF_PTR_SAFE_RETAIN(ptr)\
    \
    do\
    {\
        if (ptr)\
        {\//注释, 类型转换后retain,计数器+1
            const_cast<Ref*>(static_cast<const Ref*>(ptr))->retain();\
        }\
    \
    }   while (0);

#define CC_REF_PTR_SAFE_RELEASE(ptr)\
    \
    do\
    {\
        if (ptr)\
        {\//注释, 类型转换后retain,计数器-1
            const_cast<Ref*>(static_cast<const Ref*>(ptr))->release();\
        }\
    \
    }   while (0);

#define CC_REF_PTR_SAFE_RELEASE_NULL(ptr)\
    \
    do\
    {\
        if (ptr)\
        {\//注释, 类型转换后retain,计数器-1,并置空
            const_cast<Ref*>(static_cast<const Ref*>(ptr))->release();\
            ptr = nullptr;\
        }\
    \
    }   while (0);

/**
 * Wrapper class which maintains a strong reference to a cocos2dx cocos2d::Ref* type object.
 * Similar in concept to a boost smart pointer.
 *
 * Enables the use of the RAII idiom with Cocos2dx objects and helps automate some of the more 
 * mundane tasks of pointer initialization and cleanup.
 *
 * The class itself is modelled on C++ 11 std::shared_ptr, and trys to keep some of the methods 
 * and functionality consistent with std::shared_ptr.
 */
template <typename T> class RefPtr
{
public:
    
    inline RefPtr()
    :
        _ptr(nullptr)
    {//注释, 构造函数,无参,默认赋空
        
    }
    
    inline RefPtr(RefPtr<T> && other)
    {//注释, *移动*构造函数, c++11新特性。仅仅是_ptr拥有权的转移,转移之后_ptr被this拥有,other不再拥有。故计数器保持了不变(多了个owner,立马又少了个owner,持有者个数没变,即引用计数不变。)。
        _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.
    {//注释, 构造函数,ptr多了给owner,故需计数器+1
        CC_REF_PTR_SAFE_RETAIN(_ptr);
    }
    
    inline RefPtr(std::nullptr_t ptr)
    :
        _ptr(nullptr)
    {//注释, 构造函数,获得一个空的 ptr
        
    }
    
    inline RefPtr(const RefPtr<T> & other)
    :
        _ptr(other._ptr)
    {//注释, 拷贝构造函数,other._ptr的owner又多了一个,故引用计数+1
        CC_REF_PTR_SAFE_RETAIN(_ptr);
    }
    
    inline ~RefPtr()
    {//注释, 析构函数,这个owner挂了,计数器-1。
        CC_REF_PTR_SAFE_RELEASE_NULL(_ptr);
    }
    
    inline RefPtr<T> & operator = (const RefPtr<T> & other)
    {//注释, 操作符重载,release原来的,重新own一个新的
        if (other._ptr != _ptr)//this和other持有的_ptr如果本来就是同一个对象,就没必要下面的操作了。 操作了结果一样,浪费。
        {
            CC_REF_PTR_SAFE_RETAIN(other._ptr);//other的_ptr多了给新东家,故计数+1。且先retain是有原因滴,要是没上面的if,且_ptr的引用计数=1,先release的话就野指针了。。。
            CC_REF_PTR_SAFE_RELEASE(_ptr);//release原来的
            _ptr = other._ptr;
        }
        
        return *this;
    }
    
    inline RefPtr<T> & operator = (RefPtr<T> && other)
    {//注释, *移动*赋值运算符,c++11新特性。持有关系转移。计数器不变。类似移动构造函数。
        if (&other != this)
        {
            CC_REF_PTR_SAFE_RELEASE(_ptr);//release原来的
            _ptr = other._ptr;//重新own一个新的
            other._ptr = nullptr;//断开原来的持有关系
        }
        
        return *this;
    }
    
    inline RefPtr<T> & operator = (T * other)
    {//注释, 操作符重载,类比 inline RefPtr<T> & operator = (const RefPtr<T> & other)
        if (other != _ptr)
        {
            CC_REF_PTR_SAFE_RETAIN(other);
            CC_REF_PTR_SAFE_RELEASE(_ptr);
            _ptr = const_cast<typename std::remove_const<T>::type*>(other);     // Const cast allows RefPtr<T> to reference objects marked const too.
        }
        
        return *this;
    }
    
    inline RefPtr<T> & operator = (std::nullptr_t other)
    {//注释, 操作符重载,重新own了个空的,好吧,release并置空。
        CC_REF_PTR_SAFE_RELEASE_NULL(_ptr);
        return *this;
    }
    
    // Note: using reinterpret_cast<> instead of static_cast<> here because it doesn't require type info.
    // Since we verify the correct type cast at compile time on construction/assign we don't need to know the type info
    // here. Not needing the type info here enables us to use these operations in inline functions in header files when
    // the type pointed to by this class is only forward referenced.
    //注释, 操作符重载,第一次没看懂。。。貌似在标准库里也没这个。。。
    //在看测试代码时明白了,这是一个RefPtr到T*的转换操作符
    inline operator T * () const { return reinterpret_cast<T*>(_ptr); }
    //注释, 操作符重载, eg. (*p).doXX(); 注意对比下一个重载的操作符->
    inline T & operator * () const
    {
        CCASSERT(_ptr, "Attempt to dereference a null pointer!");
        return reinterpret_cast<T&>(*_ptr);
    }
    //注释, 操作符重载,使用智能指针这个栈变量就像用指针一样。eg. RefPtr p(new XXRef()); p->doXX()
    inline T * operator->() const
    {
        CCASSERT(_ptr, "Attempt to dereference a null pointer!");
        return reinterpret_cast<T*>(_ptr);
    }
    //注释, get
    inline T * get() const { return reinterpret_cast<T*>(_ptr); }
    
    //注释, 操作符重载,一堆堆的比较操作符,好理解。
    inline bool operator == (const RefPtr<T> & other) const { return _ptr == other._ptr; }
    
    inline bool operator == (const T * other) const { return _ptr == other; }
    
    inline bool operator == (typename std::remove_const<T>::type * other) const { return _ptr == other; }
    
    inline bool operator == (const std::nullptr_t other) const { return _ptr == other; }
    
    
    inline bool operator != (const RefPtr<T> & other) const { return _ptr != other._ptr; }
    
    inline bool operator != (const T * other) const { return _ptr != other; }
    
    inline bool operator != (typename std::remove_const<T>::type * other) const { return _ptr != other; }
    
    inline bool operator != (const std::nullptr_t other) const { return _ptr != other; }
    
    
    inline bool operator > (const RefPtr<T> & other) const { return _ptr > other._ptr; }
    
    inline bool operator > (const T * other) const { return _ptr > other; }
    
    inline bool operator > (typename std::remove_const<T>::type * other) const { return _ptr > other; }
    
    inline bool operator > (const std::nullptr_t other) const { return _ptr > other; }
    
    
    inline bool operator < (const RefPtr<T> & other) const { return _ptr < other._ptr; }
    
    inline bool operator < (const T * other) const { return _ptr < other; }
    
    inline bool operator < (typename std::remove_const<T>::type * other) const { return _ptr < other; }
    
    inline bool operator < (const std::nullptr_t other) const { return _ptr < other; }
    
        
    inline bool operator >= (const RefPtr<T> & other) const { return _ptr >= other._ptr; }
    
    inline bool operator >= (const T * other) const { return _ptr >= other; }
    
    inline bool operator >= (typename std::remove_const<T>::type * other) const { return _ptr >= other; }
    
    inline bool operator >= (const std::nullptr_t other) const { return _ptr >= other; }
    
        
    inline bool operator <= (const RefPtr<T> & other) const { return _ptr <= other._ptr; }
    
    inline bool operator <= (const T * other) const { return _ptr <= other; }
    
    inline bool operator <= (typename std::remove_const<T>::type * other) const { return _ptr <= other; }
    
    inline bool operator <= (const std::nullptr_t other) const { return _ptr <= other; }
    
    //注释, 操作符重载。使智能指针可用于bool表达式, eg: if(p && p->doxx()) {}.
    inline operator bool() const { return _ptr != nullptr; }
        
    inline void reset()
    {
    	//注释, 放弃对_ptr对持有。计数-1,并置空。防止重复调用。
        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;
        }
    }
    
    //注释, 特殊情况的应用。注释已经很详细了。从引用计数角度看。
    /**
     * This function assigns to this RefPtr<T> but does not increase the reference count of the object pointed to.
     * Useful for assigning an object created through the 'new' operator to a RefPtr<T>. Basically used in scenarios
     * where the RefPtr<T> has the initial ownership of the object.
     *
     * E.G:
     *      RefPtr<cocos2d::Image> image;
     *      image.weakAssign(new cocos2d::Image());
//注意weakAssign的参数是一个引用,这里的计数器变化过程是:
//new image时,计数器是1;调用weakAssign时构造一个临时的RefPtr对象,调用构造函数inline RefPtr(T * ptr),计数器+1;
//weakAssign函数调用结束,临时对象生命周期结束,计数器-1。
//weakAssign函数调用结束后,image的计数器=1
//以上已经加log验证。
     *
     * Instead of:
     *      RefPtr<cocos2d::Image> image;
     *      image = new cocos2d::Image();//new Image的时候计数器=1;赋给image时是通过重载的赋值运算符inline RefPtr<T> & operator = (T * other),这里计数器又加1了。
     *                                   //计数器变为2,但owner只有1个,故需要release,让计数器2-1=1
     *      image->release();               // Required because new'd object already has a reference count of '1'.
     */
    inline void weakAssign(const RefPtr<T> & other)
    {
        CC_REF_PTR_SAFE_RELEASE(_ptr);
        _ptr = other._ptr;
    }
    
private:
    Ref * _ptr;
};
    
/**
 * Cast between RefPtr types statically.
 */
template<class T, class U> RefPtr<T> static_pointer_cast(const RefPtr<U> & r)
{
    return RefPtr<T>(static_cast<T*>(r.get()));
}

/**
 * Cast between RefPtr types dynamically.
 */
template<class T, class U> RefPtr<T> dynamic_pointer_cast(const RefPtr<U> & r)
{
    return RefPtr<T>(dynamic_cast<T*>(r.get()));
}

/**
 * Done with these macros.
 */
#undef CC_REF_PTR_SAFE_RETAIN
#undef CC_REF_PTR_SAFE_RELEASE
#undef CC_REF_PTR_SAFE_RELEASE_NULL

NS_CC_END

#endif  // __CC_REF_PTR_H__
  • 简单归纳下代码
    1. 3个计数器相关的宏:+1;-1;-1并置空。
    2. 成员变量是Ref * _ptr;
    3. 一组构造函数,差异在参数(无参,空参,右值引用,左值引用, T*)
      1. RefPtr()
      2. RefPtr(std::nullptr_t ptr)
      3. RefPtr(RefPtr && other)
      4. RefPtr(const RefPtr & other)
      5. RefPtr(T * ptr)
    4. 一个析构函数
    5. 一组赋值运算符重载(空参,右值引用,左值引用, T*)
      1. RefPtr & operator = (std::nullptr_t other)
      2. RefPtr & operator = (RefPtr && other)
      3. RefPtr & operator = (const RefPtr & other)
      4. RefPtr & operator = (T * other)
    6. *重载, eg. (*p).doXX()
    7. ->重载,eg. p->doXX()
    8. RefPtr到T*的转换操作符inline operator T * () const { return reinterpret_cast<T*>(_ptr); }
    9. 比较操作符重载== != > < <= >=
    10. 布尔操作符重载(bool) eg: if(p && p->doxx()) {}
    11. get, reset
    12. weakAssign, eg. RefPtr<cocos2d::Image> image;image.weakAssign(new cocos2d::Image());

如何用RefPtr

  • 代码片段from RefPtrTest(3.3final)
片段1
    // TEST(constructors)
    {
        // Default constructor
        RefPtr<Ref> ref1;
        CC_ASSERT(nullptr == ref1.get());

        // Parameter constructor
        RefPtr<__String> ref2(cocos2d::String::create("Hello"));
        CC_ASSERT(strcmp("Hello", ref2->getCString()) == 0);
        CC_ASSERT(2 == ref2->getReferenceCount());//create时为1,赋给RefPtr再加了个1,故=2

        // Parameter constructor with nullptr
        RefPtr<__String> ref3(nullptr);
        CC_ASSERT((__String*) nullptr == ref3.get());
        
        // Copy constructor
        RefPtr<__String> ref4(ref2);//多了个owner,计数器+1
        CC_ASSERT(strcmp("Hello", ref4->getCString()) == 0);
        CC_ASSERT(3 == ref2->getReferenceCount());
        CC_ASSERT(3 == ref4->getReferenceCount());//ref2 ref4持有同一个对象
        
        // Copy constructor with nullptr reference
        RefPtr<Ref> ref5(ref1);
        CC_ASSERT((Ref*) nullptr == ref5.get());
    }
//超出这个作用域,有内存泄漏吗?
//ref1 ref3 ref5没持有堆上的资源,不存在泄漏;
//ref2 ref4持有同一个string 'hello',他们的生命周期结束时各自release一次。
//字符串的引用计数=3-2=1,但Ref的释放是计数器等于0时才delete啊,内存泄漏了?!
//答案是否定的,这就得用之前的auto-release-pool的知识了,因为'hello'在被create的时候被放入了pool里面,
//所以在mainLoop的下次清理时,会自动被pool释放。
//这也看出,
//1. cocos的智能指针照上面的用法,针对cocos create出来的东西进行管理其实是
//白搭。真正起作用的是auto-release-pool。(但想想一种情况,若智能指针的作用范围放大,那真正起作用的可能就是智能指针了。)
//2. cocos的create机制创建的对象(未被addChild等操作导致计数器变化)可以理解为'临时对象',
//这类对象会被pool自动管理释放。
//3. 所以cocos的智能指针RefPtr针对new出来的Ref的子类有个weakAssign方法。
//(因为new出来的就跟自动释放池一毛钱关系没有,那RefPtr就得全全负责到底了
//。但矛盾出来了,通过RefPtr的普通构造函数来实现,这个new的对象计数器就跳到2了。
//然后的然后就是很奇怪的方法weekAssign诞生了。。。
//如果cocos的sdk里面的Ref对象都只能用create方式来创建呢,会不会和谐很多。。。)



继续片段2
    // TEST(destructor)
    {
        __String * hello = __String::create("Hello");
        CC_ASSERT(1 == hello->getReferenceCount());
        
        {
            RefPtr<Ref> ref(hello);
            CC_ASSERT(2 == hello->getReferenceCount());
        }
        
        CC_ASSERT(1 == hello->getReferenceCount());
    }
//这段没啥好讲的,再次说明hello是被auto-release-pool释放的。


片段3
    // TEST(assignmentOperator)
    {
        // Test basic assignment with pointer
        RefPtr<__String> ref1;
        CC_ASSERT((Ref*) nullptr == ref1.get());
        
        ref1 = __String::create("World");
        CC_ASSERT(strcmp("World", ref1->getCString()) == 0);
        CC_ASSERT(2 == ref1->getReferenceCount());
        
        // Assigment back to nullptr
        __String * world = ref1;
        CC_ASSERT(2 == world->getReferenceCount());
       
       ...略
    }
//吸引我注意力的是这句__String * world = ref1; 貌似他就是上面第一次没理解的问题的答案。
//inline operator T * () const { return reinterpret_cast<T*>(_ptr); }
//大胆猜测,难道这是将智能指针转换为原来类型的操作符重载??
//答案是,是滴。。。







剩下的代码就不挨个看了。。。

小结

  • 两段构造方式create+Ref+pool,使create出的对象像个栈上的局部变量。

    • (mainLoop下次清理pool时自动删除)
  • 两段构造方式create+Ref+pool+RefPtr,使得create出的对象有两个保姆照顾了。

    • (要么mainLoop下次清理pool时自动删除,要么被智能指针删除,取决于智能指针的生命周期了)
  • new+Ref+RefPtr,RefPtr提供了个weakAssign方法用于这种创建方式。

    • (被智能指针全权负责)
  • 不像shared_ptr的引用计数外部不能访问,在cocos中使用RefPtr就要小心操作retain/release了

    • eg
    auto str=new __String(“Hello”);
    RefPtr<__String> ptr;
    ptr.weakAssign(str);
    str.release();        //原则,既然交个智能指针负责了,操作就要直接对智能指针进行操作。不要再对原始指针操作。
    (*ptr)->getCString(); //访问野指针,将会报错
    
  • RefPtr提供了一个操作符号重载,允许智能指针转换为原始指针,也增加了误用的风险。建议将这种转换结果当临时变量用用就好。

  • 在开发时也可以用shared_ptr。

  • 提一提一个例子,来自cocos网站

    RefPtr<__String> ptr1(new __String(“Hello”)); //引用计数2
    RefPtr<__String> ptr2;
    ptr2.weakAssign(ptr1); //引用计数2
    ptr2.reset(); //引用计数1
    ptr2.reset(); //被释放 --------->3.3中,这里是不会有影响的,因为上面reset的时候,已经将指针置空。 或许是曾经某个版本的bug,fix了。
    (*ptr1)->getCString(); //导致错误---->故这里没问题
    
  • 最后,前方一定有各种陷阱等着你我。。。加油!

扩展阅读

Clone this wiki locally