Ogre(1.9)的内存管理

Ogre(1.9)的内存管理

内存管理分类

在Ogre里面,(堆)内存管理分为两类:
1. 内存分配器的管理
2. 垃圾回收

以下的内存管理,指的都是 堆内存的管理

内存管理的作用

  • 提高内存分配的效率,有标准库的new/delete,也有nedmalloc支持多线程的分配器,还引入了内存池,在不同平台中,使用最适合分配器来提高效率。
  • 提高引擎的可扩展性,用户可以根据系统平台的实际情况,随意更改内存分配器,甚至是添加自己的内存分配器。
  • 提高引擎的可维护性,Ogre内存分配器里面引入一些Debug跟踪函数,用于监控是否有内存泄露,这样方便开发者在内存方面的调试。
  • 提高开发效率,例如Ogre可以使用SharedPtr这个智能指针,虽然有环形引用的问题,但是开发者可以不用new完之后一直担心着要delete。

现在可能不太能理解,等看完这章后,就会feel到这些特性。

源码分析——内存分配器

源码文件

  • OgreMemoryAllocatedObject.h
    OgreMemoryAllocatedObject.cpp
    里面包含 类AllocatedObject,在Ogre里面所有的类都应该继承它,才可以使用Ogre提供的内存管理功能

  • OgreMemoryAllocatorConfig.h
    这个文件用一堆宏来控制了使用哪个底层的内存分配器,还有Debug和Release模式下不同的内存分配函数

  • OgreMemoryNedAlloc.h
    OgreMemoryNedAlloc.cpp
    封装了nedmalloc普通内存分配器,有内存对齐和不对齐两个封装

  • OgreMemoryNedPooling.h
    OgreMemoryNedPooling.cpp
    封装了nedmalloc内存池的内存分配器,有内存对齐和不对齐两个封装

  • OgreMemoryStdAlloc.h
    封装了标准库的内存分配器(new/delete),有内存对齐和不对齐两个封装

  • OgreMemorySTLAllocator.h
    用于提供Ogre自身实现的STL库提供内存管理

  • OgreAlignedAllocator.h
    OgreAlignedAllocator.cpp
    标准库内存分配器(new/delete)改造成内存对齐的hack

  • OgreMemoryTracker.h
    OgreMemoryTracker.cpp
    在Debug模式下,提供内存跟踪,用来测试是否有内存泄露

Ogre类之祖——AllocatedObject

        在Ogre这套系统中,很多类其实或间接或者直接,都继承了AllocatedObject这个模板类。

// OgreMemoryAllocatedObject.h
template <class Alloc>
class _OgreExport AllocatedObject
{
public:
    explicit AllocatedObject()
    { }

    ~AllocatedObject()
    { }

    /// operator new, with debug line info
    void* operator new(size_t sz, const char* file, int line, const char* func)
    {
        return Alloc::allocateBytes(sz, file, line, func);
    }

    void* operator new(size_t sz)
    {
        return Alloc::allocateBytes(sz);
    }

    /// placement operator new
    void* operator new(size_t sz, void* ptr)
    {
        (void) sz;
        return ptr;
    }

    /// array operator new, with debug line info
    void* operator new[] ( size_t sz, const char* file, int line, const char* func )
    {
        return Alloc::allocateBytes(sz, file, line, func);
    }

    void* operator new[] ( size_t sz )
    {
        return Alloc::allocateBytes(sz);
    }

    void operator delete( void* ptr )
    {
        Alloc::deallocateBytes(ptr);
    }

    // Corresponding operator for placement delete (second param same as the first)
    void operator delete( void* ptr, void* )
    {
        Alloc::deallocateBytes(ptr);
    }

    // only called if there is an exception in corresponding 'new'
    void operator delete( void* ptr, const char* , int , const char*  )
    {
        Alloc::deallocateBytes(ptr);
    }

    void operator delete[] ( void* ptr )
    {
        Alloc::deallocateBytes(ptr);
    }


    void operator delete[] ( void* ptr, const char* , int , const char*  )
    {
        Alloc::deallocateBytes(ptr);
    }
};

        从上面可以看出,整个new/delete运算符都给重载了,这些函数里面都用Alloc给分配内存空间了。看到这里,我们知道为啥这要是个模板类了。但新的问题又来了,Alloc是个什么东西呢?
        这个问题先放一放,我们看下这个头文件对应的cpp里面是什么东西。

// OgreMemoryAllocatedObject.cpp

    /*
    Ugh, I wish I didn't have to do this.
    The problem is that operator new/delete are *implicitly* static. We have to instantiate them for each combination exactly once throughout all the compilation units that are linked together, and this appears to be the only way to do it. 
    At least I can do it via templates.
    */
    template class AllocatedObject<GeneralAllocPolicy>;
    template class AllocatedObject<GeometryAllocPolicy>;
    template class AllocatedObject<AnimationAllocPolicy>;
    template class AllocatedObject<SceneCtlAllocPolicy>;
    template class AllocatedObject<SceneObjAllocPolicy>;
    template class AllocatedObject<ResourceAllocPolicy>;
    template class AllocatedObject<ScriptingAllocPolicy>;
    template class AllocatedObject<RenderSysAllocPolicy>;

        soga,Alloc是填了这些东西,我们接着研究这些AllocPolicy。不过,在此之前,仔细的同学可能会发现一个问题。一个cpp文件里面放这些声明语句是为了什么呢?
        看看源码里面的注释,我们就可以发现,这些语句其实是为了实现这些模板类的new/delete这些方法,方便别的cpp文件使用这些new/delete方法时可以链接这个cpp,防止这些方法只是声明而没有实现,导致链接出错。 接下来,我们进入这一堆AllocPolicy。
        在OgreMemoryAllocatorConfig.h里面,这些AllocPolicy都是 CategorisedAllocPolicy这个模板类的特例化的类型。

// OgreMemoryAllocatorConfig.h
#if OGRE_MEMORY_ALLOCATOR == OGRE_MEMORY_ALLOCATOR_NEDPOOLING
#  include "OgreMemoryNedPooling.h"
namespace Ogre
{
    // configure default allocators based on the options above
    // notice how we're not using the memory categories here but still roughing them out
    // in your allocators you might choose to create different policies per category
    // configurable category, for general malloc
    // notice how we ignore the category here, you could specialise
    template <MemoryCategory Cat> class CategorisedAllocPolicy : public NedPoolingPolicy{};
    template <MemoryCategory Cat, size_t align = 0> class CategorisedAlignAllocPolicy : public NedPoolingAlignedPolicy<align>{};
}
...

        由于这文件里面用了宏定义来选择各种内存分配模式,我这里只是截取其中一个宏的选择。在我选取的一个代码片段中,可以看出CategorisedAlignAllocPolicy是继承于 NedPoolingPolicy 或者 NedPoolingAlignedPolicy这两个类。然而,在其他的宏的分支下中,也是类似的做法,但是是继承其他的AllocPolicy类。

// define the memory allocator configuration to use
#define OGRE_MEMORY_ALLOCATOR_STD 1
#define OGRE_MEMORY_ALLOCATOR_NED 2
#define OGRE_MEMORY_ALLOCATOR_USER 3
#define OGRE_MEMORY_ALLOCATOR_NEDPOOLING 4

        OGRE定义了4种内存分配方法,STD 顾名思义即为标准库提供的内存分配器,NED和NEDPOOLING则是使用了nedmalloc这个相对专业内存分配器(支持多线程,nedmalloc就不详细展开了),USER代表用户自定义的内存分配器,源码中为空,留给开发者自己补充。
        OGRE通过宏OGRE_MEMORY_ALLOCATOR来选择分配器的类型,在我的编译平台下,它的缺省值是4,使用的nedmalloc分配器中的内存池分配法,这也是为何我上面截取的那个片段里面的CategorisedAllocPolicy是继承NedPoolingPolicy的。

        目前,我们对这个Ogre这个内存分配器的设计模式有个大体的了解了:
1. 所有的类都继承AllocatedObject,这些类的new/delete这些内存管理函数都被AllocatedObject里面的Alloc接管了。
2. Ogre把需要分配内存的资源分了几类,为了统一管理这几类资源,用一个CategorisedAlignAllocPolicy来做中介,然后配合枚举类型MemoryCategory做分类。Alloc对应着这些不同枚举值的CategorisedAlignAllocPolicy。
3. Ogre使用宏的方法,使得不同枚举值的CategorisedAlignAllocPolicy**继承于不同的Policy类**,让不同种类的资源有不同的内存分配方法。

综上所述,Ogre可以为不同类型的资源提供不同类型的内存分配器,以适应不同资源的内存分配场景。下面我们就来分析下这些不同类型的内存分配器。

Ogre的内存分配器

        由上面,我们可以知道:OGRE定义了4种内存分配方法,STD,NED,NEDPOOLING 和 USER。我们抛开没有源码实现的USER,仔细研究下STD,NED 和 NEDPOOLING。
        这里我就直接上一个总结,方便下面的理解:Ogre中所有的分配方式都分为对齐分配内存与不对齐两种class,取名方式是XXAlignedPolicy与XXAllocPolicy,并且所有的内存函数都是静态(static)的。这些类的成员函数固定为以下三个:

static void* allocBytes(size_t count, const char* file, int line, const char* func);
static void deallocBytes(void* ptr);
static size_t getMaxAllocationSize();
STD内存分配器

头文件 OgreMemoryStdAlloc.h / OgreAlignedAllocator.h

        不对齐分配方式那个没啥好说的,就只是用标准库的malloc/free去实现allocBytes和deallocBytes这两个函数就草草了事了。
        然而,内存对齐那里的代码写得非常精辟,非常值得研究和分享。

template <size_t Alignment = 0>
class StdAlignedAllocPolicy
{
public:
    // compile-time check alignment is available.
    typedef int IsValidAlignment
        [Alignment <= 128 && ((Alignment & (Alignment-1)) == 0) ? +1 : -1];
...

        一开始不认真看,没看懂IsValidAlignment是什么类型。仔细思考,才发觉这是个 数组类型。配合上面的注释,我们可以了解到,这个神奇的数组类型,为了在编译时检查Alignment的值是否合法,由源码其他地方我们可以知道,合法的Alignment是 0 <= Alignment <= 128,而且Alignment是2的倍数。实际它是如何做到的呢?我们来剖析一下:
* 这个数组声明里面,用了一个问号表达式,条件成立的话,表达式的值为1,不成立为-1。我们都知道,声明数组的长度至少是1,所以,当条件不成立的时候,编译器会报错
* Alignment <= 128,肯定都懂,但是((Alignment & (Alignment-1)) == 0是什么意思呢?把一个数当做二进制数来看,一般的数字减去1,只会最后几位二进制位改变(取反)了。唯有2的倍数这些数字,是全部二进制位都改变(取反)了,此时再做 与 运算,肯定为0了。就这么精辟地,把Alignment是2的倍数这个条件给满足了。

为了实现在标准库下的内存对齐分配器,Ogre还专门多加了一个类AlignedMemory(头文件OgreAlignedAllocator.h),alignment为0是使用SIMD默认的数据长度,我们先看看它的allocate函数。

void* AlignedMemory::allocate(size_t size, size_t alignment)
{
    assert(0 < alignment && alignment <= 128 && Bitwise::isPO2(alignment));

    unsigned char* p = new unsigned char[size + alignment];
    size_t offset = alignment - (size_t(p) & (alignment-1));

    unsigned char* result = p + offset;
    result[-1] = (unsigned char)offset;

    return result;
}

它的巧妙点是:

  • 比需要分配的内存大小多了alignment,多的这些空间是用了调整 指针 到 内存地址为alignment倍数的地方,即alignment大小的内存对齐。
  • 多出的地方,还用于保存调整的偏移量offset,result[-1]这一句就是用来存放offset的,用于delete时候还原原来分配的地址
  • offset的计算方法非常快速,可以理解成,取出new出来地址在alignment内存地址对齐后,最后多出来的那几个二进制位,然后再用alignment减去,即可得到偏移量。这样计算的话,如果是new出来的地址也内存对齐了,那么它的偏移量是整个alignment。这样在result指针前,还是至少可以有一个字节来保存offset
void AlignedMemory::deallocate(void* p)
{
    if (p)
    {
        unsigned char* mem = (unsigned char*)p;
        mem = mem - mem[-1];
        delete [] mem;
    }
}

这个很好理解,取出offset,然后把指针偏移会到原始位置,再delete。

NED内存分配器

源码文件: OgreMemoryNedAlloc.h / OgreMemoryNedAlloc.cpp

  • nedmalloc是一个可选的malloc内存分配的实现,主要是适应多线程无锁操作。主页是http://www.nedprod.com/

  • NedAllocPolicy 是内存不对齐的,NedAlignedAllocPolicy 是内存对齐的。

  • 看完STD的源码分析后,再来看NED,其实也差不多,类的结构也基本一致。

  • NED底层的内存分配,靠一个叫NedAllocImpl的类,这个类封装了nedmalloc里面一些非内存池的函数,相当于STD中AlignedMemory的地位。其实,这样的做法我觉得没什么必要,还不如让NedAllocPolicy 和 NedAlignedAllocPolicy直接调用nedmalloc,来得简单高效,中间套多一层NedAllocImpl,对这种频发调用的函数来说,无疑是低效的。

  • 由于Nedmalloc中都已经有相应的对齐分配的api,所以在这种分配方式下直接调用就可以了。

NEDPOOLING内存分配器

源码文件: OgreMemoryNedPooling.h / OgreMemoryNedPooling.cpp

  • 和NED几乎一样,也十分容易理解,不太相同的只是NedPoolingImpl的实现部分用的是nedmalloc的内存池

  • NedPoolingPolicy 是内存不对齐的,NedPoolingAlignedPolicy 是内存对齐的。

  • NedPoolingImpl中的函数,这些函数都是在_NedPoolingIntern namespace中的全局函数,也就是说_NedPoolingIntern 命名空间中的函数才是真正调用ned的代码。

  • 命名空间_NedPoolingIntern中定义了内存池的大小,默认为14,还保存了pool的数组以及初始化内存池时用到的footprint。

Ogre的内存跟踪

        在DEBUG模式下,Ogre会对开发者分配的内存进行跟踪,这样就可以很方便知道哪里内存泄露了。现在我们来谈下他的实现原理。
        由上可知,所有继承AllocatedObject的类的堆内存分配,都可以轻易给他指定一个内存分配器。当我们需要添加堆内存块的跟踪的时候,稍微修改下这些内存分配器即可,Ogre的设计模式强大之处就在于此

static void* allocBytes(size_t count, const char* file, int line, const char* func);

        这个就是上面的内存分配器三个固定的成员函数之一。仔细点,就发现这个函数有问题,多了很多参数!!!这些参数有啥用呢?我们追下去看看就懂了。

// 在allocBytes函数里
#if OGRE_MEMORY_TRACKER
    MemoryTracker::get()._recordAlloc(ptr, count, 0, file, line, func);
#else
    // avoid unused params warning
    file = func = "";
    line = 0;
#endif

// 在deallocBytes函数里
#if OGRE_MEMORY_TRACKER
    MemoryTracker::get()._recordDealloc(ptr);
#endif

        这些参数都被传进了MemoryTracker,而MemoryTracker正是用来记录这些刚刚被分配出来的内存的。如果有内存被recordAlloc,但是没有被recordDealloc,那就是标准的内存泄露了。

        在OgreMemoryAllocatorConfig.h文件里,有一堆宏定义,在不同模式下的 OGRE_NEW_T、OGRE_NEW_ARRAY_T、OGRE_DELETE_T、OGRE_DELETE_ARRAY_T等,有不同的传入参数。如 Debug模式下,宏定义给它们传入了__FILE__, __LINE__, __FUNCTION__ 这些参数,这些也正是allocBytes函数用来给recordAlloc提供跟踪信息的参数
        所以,为了更好的使用内存跟踪这个功能,当一个类继承了AllocatedObject,应该使用 OGRE_NEW 来创建对象,而不是直接new

神之OGRE_NEW_T

        在Ogre中,继承AllocatedObject来实现内存分配器的管理,有个很大的问题,就是每个类都得继承这个AllocatedObject,否则没法使用这个内存分配器。这样的设计对其他代码库的整合和游戏开发者代码的重用性等带来了挑战。如,游戏逻辑相关的类进行向其他引擎迁移的时候,也必须把这整一套AllocatedObject给迁移过去,这样肯定是不行的。

// OgreMemoryAllocatorConfig.h

/// Allocate a block of raw memory, and indicate the category of usage
#   define OGRE_MALLOC(bytes, category) ::Ogre::CategorisedAllocPolicy<category>::allocateBytes(bytes, __FILE__, __LINE__, __FUNCTION__)
/// Allocate a block of memory for a primitive type, and indicate the category of usage
#   define OGRE_ALLOC_T(T, count, category) static_cast<T*>(::Ogre::CategorisedAllocPolicy<category>::allocateBytes(sizeof(T)*(count), __FILE__, __LINE__, __FUNCTION__))
/// Free the memory allocated with OGRE_MALLOC or OGRE_ALLOC_T. Category is required to be restated to ensure the matching policy is used
#   define OGRE_FREE(ptr, category) ::Ogre::CategorisedAllocPolicy<category>::deallocateBytes((void*)ptr)

/// Allocate space for one primitive type, external type or non-virtual type with constructor parameters
#   define OGRE_NEW_T(T, category) new (::Ogre::CategorisedAllocPolicy<category>::allocateBytes(sizeof(T), __FILE__, __LINE__, __FUNCTION__)) T
/// Allocate a block of memory for 'count' primitive types - do not use for classes that inherit from AllocatedObject
#   define OGRE_NEW_ARRAY_T(T, count, category) ::Ogre::constructN(static_cast<T*>(::Ogre::CategorisedAllocPolicy<category>::allocateBytes(sizeof(T)*(count), __FILE__, __LINE__, __FUNCTION__)), count) 
/// Free the memory allocated with OGRE_NEW_T. Category is required to be restated to ensure the matching policy is used
#   define OGRE_DELETE_T(ptr, T, category) if(ptr){(ptr)->~T(); ::Ogre::CategorisedAllocPolicy<category>::deallocateBytes((void*)ptr);}
/// Free the memory allocated with OGRE_NEW_ARRAY_T. Category is required to be restated to ensure the matching policy is used, count and type to call destructor
#   define OGRE_DELETE_ARRAY_T(ptr, T, count, category) if(ptr){for (size_t b = 0; b < count; ++b) { (ptr)[b].~T();} ::Ogre::CategorisedAllocPolicy<category>::deallocateBytes((void*)ptr);}

        Ogre引擎的开发者也意识到了这点,加入了神奇的一个宏定义——OGRE_NEW_T、OGRE_NEW_ARRAY_T、OGRE_DELETE_T、OGRE_DELETE_ARRAY_T等等这些带_T结尾的宏。
        开发者只需要用这些带_T结尾的宏,来代替new/delete、malloc/free,就可以不用继续间接或者直接地继承那个可恶的AllocatedObject了。这些宏定义,是如何做到的呢?
        其实现方法,其实也很简单,用的是C++里面一个叫做 placement new的方法,假设p为void* 类型,指向一个合法的内存,T是我们要实例化的类,然后我们可以使用 new(p) T 来实例化 类T。同时,实例化后的T对象,保存在p指向那块内存那里 的。Array的分配这些,也是用了这个原理,再加入一些 模板函数和for语句 去实现Array的内存分配。
        所以,为了更好的使用Ogre的定义的内存分配器,当一个类没有继承了AllocatedObject,应该使用 OGRE_NEW_T 来创建对象,而不是直接new

STL之hack

        C++中的STL库,无论是什么程序,或多或少都会用到的。Ogre引擎也不例外,但是Ogre有自己的内存管理方式,STL库也有自己的内存管理方法。不过STL库设计得太完美了,完美到可以随便替换掉内存分配器,Ogre只需要提供一个符合STL接口的Allocator就行。
        OgreMemorySTLAllocator.h 里面的STLAllocator类,是按照标准的STL库里面的Allocator的接口来做的。然后这个STLAllocator类在OgrePrerequisites.h文件中被用来替换STL中默认的Allocator。

template <typename T, typename A = STLAllocator<T, GeneralAllocPolicy> > 
struct vector 
{ 
#if OGRE_CONTAINERS_USE_CUSTOM_MEMORY_ALLOCATOR
    typedef typename std::vector<T, A> type;
    typedef typename std::vector<T, A>::iterator iterator;
    typedef typename std::vector<T, A>::const_iterator const_iterator;
#else
    typedef typename std::vector<T> type;
    typedef typename std::vector<T>::iterator iterator;
    typedef typename std::vector<T>::const_iterator const_iterator;
#endif
}; 

template <typename T, typename A = STLAllocator<T, GeneralAllocPolicy> > 
struct list 
{ 
#if OGRE_CONTAINERS_USE_CUSTOM_MEMORY_ALLOCATOR
    typedef typename std::list<T, A> type;
    typedef typename std::list<T, A>::iterator iterator;
    typedef typename std::list<T, A>::const_iterator const_iterator;
#else
    typedef typename std::list<T> type;
    typedef typename std::list<T>::iterator iterator;
    typedef typename std::list<T>::const_iterator const_iterator;
#endif
};

        所以,在Ogre中,要使用STL,千万别使用std里面的,而是用使用Ogre::XXX::type,XXX可以是 deque,vector,list,map,set,multimap。这样,才能让内存被统一管理。

Ogre内存分配的缺陷

        Ogre这样的内存管理模式,看起来好像挺完美的,其实并不是。
        这种入侵式的代码风格,不利于其他代码库的整合。如好好的STL库,为了使用Ogre的内存管理,也得自己重新进行hack一下。 不过这也不能怪Ogre,这本身就是C++的通病:不同的模块之间的内存管理,如果没有协商好,各自都会有自己的一套方法,让各个模块的整合变得十分困难,很容易造成内存泄露。不像别的一些有自动内存管理的语言,像java、js、python。
        不过,我有个问题,就是为何Ogre不直接把全局的new和delete给重载了,那样就可以解决这些问题了。才疏学浅,没法解释这一点,也有可能是Ogre为了不干扰别的模块自身的内存管理吧。

源码分析——智能指针

        内存管理中的 垃圾回收,是C++这种语言永恒的话题。
        C++开发人员,每时每刻都会被这些new/delete折磨,时时刻刻都要考虑着,new完后什么时候delete,在哪里delete。要么new完忘了delete,要么delete了没有new的,或者是delete了两次。
        目前,有两大解决方法:引用计数标记清除。其实,这两种方法其实理解起来都不难,但是遇到多线程问题的时候,就没这么简单了。
        标记清除法,效果几乎是最好的,但一开始回收,整个进程都会被暂停。所以,这种方法有很多成熟的变种,比如java的jvm里面的分代回收的。详细的就不展开了。
        引用计数法,才是主角,因为Ogre里面可以使用这种方法来逃避new完后必须要delete的麻烦。在Ogre里面,引用计数法的实现是使用了智能指针,其实boost里面也有这个智能指针,而且考虑的情况也比Ogre多,比如考虑到 环形引用的问题,这个问题我们待会讨论。
        Ogre的智能指针是,OgreSharedPtr.h文件下的SharedPtr来实现的,先看下SharedPtr的类成员和构造函数:

template<class T> class SharedPtr
{
    template<typename Y> friend class SharedPtr;
protected:
    T*             pRep;
    SharedPtrInfo* pInfo;
    /** Constructor.
    @param rep The pointer to take ownership of
    @param inFreeMethod The mechanism to use to free the pointer
    */
    template< class Y>
    explicit SharedPtr(Y* rep, SharedPtrFreeMethod inFreeMethod = SPFM_DELETE) 
        : pRep(rep)
        , pInfo(rep ? createInfoForMethod(rep, inFreeMethod) : 0)
    {
    }
...
};
  • pRep很容易就可以知道是保存new出来的指针的
  • typename Y的友元类,是为了指针的类型转换的。比如子类转父类之类的。
  • pInfo,是一个SharedPtrInfo类型,是由createInfoForMethod来创建,我们来研究下这个函数,发现它可以创建以下三个类型的对象:SharedPtrInfoDelete、SharedPtrInfoDeleteT 和 SharedPtrInfoFree
template <class T>
class SharedPtrInfoDelete : public SharedPtrInfo
{
    T* mObject;
public:
    inline SharedPtrInfoDelete(T* o) : mObject(o) {}

    virtual ~SharedPtrInfoDelete()
    {
        OGRE_DELETE mObject;
    }
};

template <class T>
class SharedPtrInfoDeleteT : public SharedPtrInfo
{
    T* mObject;
public:
    inline SharedPtrInfoDeleteT(T* o) : mObject(o) {}

    virtual ~SharedPtrInfoDeleteT()
    {
        OGRE_DELETE_T(mObject, T, MEMCATEGORY_GENERAL);
    }
};

template <class T>
class SharedPtrInfoFree : public SharedPtrInfo
{
    T* mObject;
public:
    inline SharedPtrInfoFree(T* o) : mObject(o) {}

    virtual ~SharedPtrInfoFree()
    {
        OGRE_FREE(mObject, MEMCATEGORY_GENERAL);
    }
};

        SharedPtrInfoDelete、SharedPtrInfoDeleteT 和 SharedPtrInfoFree分别对应到三种不同的堆内存释放方法的宏定义:OGRE_DELETE、OGRE_DELETE_T 和 OGRE_FREE。结合上面的Ogre内存分配的宏定义,我们可以轻易知道为何Ogre会有三种内存释放方法了。看到这里,Ogre的SharedPtr其实是对Boost里面那个同样功能的shared_ptr补充了Ogre自己的内存释放方法。

struct SharedPtrInfo {
    inline SharedPtrInfo()
        : useCount(1)
    {}
    virtual ~SharedPtrInfo() {}
    AtomicScalar<unsigned>  useCount;
};

        这里的SharedPtrInfo 是 SharedPtrInfoDelete、SharedPtrInfoDeleteT 和 SharedPtrInfoFree 的基类,它有一个成员useCount,用来记录当前这个指针被引用了多少次。这个成员是原子操作的,这样可以解决在多线程下使用这个智能指针会发生的一些混乱问题。
        再通过分析SharedPtr的其他一些成员函数,我们可以知道SharedPtr的工作原理是:
1. 开发者先用某种方法申请一块内存,然后把这个内存的指针 和 具体这块内存的分配方法 传进SharedPtr。
2. SharedPtr 会根据 内存分配的类型,新建一个 SharedPtrInfo的子类,用于保存** 引用计数 和 指针。当引用计数为0的时候,delete这个SharedPtrInfo指针。又由于**SharedPtrInfo的析构函数为虚函数,相当于调用子类的析构函数。这样,就可以只用delete SharedPtrInfo这个父类的指针,来释放掉不同内存分配方法分配的内存。
3. SharedPtr的复制构造函数 和 赋值操作符函数,都是对已有的SharedPtrInfo里面的 引用计数 useCount进行加一,即有新的SharedPtr指向这块内存。然后,在析构函数 或者 是赋值操作符 那里减一,即有某个SharedPtr放弃了这块内存。如果useCount为0,即没有SharedPtr指向那个内存了,此时就可以delete SharedPtrInfo 来释放内存了。
4. 在SharedPtr中,大量使用assert来处理一些异常的情况,进而简化代码,这种方法是值得学习的。

        智能指针这个方法,是实现简单,对代码运行效率影响低。但它有个致命伤,那就是环形引用。
SharedPtr环形引用1
        如图,main_a 和 main_b 分别指向类A和类B的一个实例化的对象。
        main_a 指向的对象中,成员b指向 main_b指向的对象。同理,main_b 指向的对象中,成员a指向 main_a指向的对象。此时,main_a中的use_count 为 2, main_b中的use_count 也为 2。
SharedPtr环形引用2
        此时,main函数结束后,main_a 和 main_b 被释放掉了,各自的use_count 减一。可以发现,main函数都结束了,然而那两个对象的SharedPtr的use_count竟然还是1,没法被析构,最后导致内存泄露。
        这就是所谓的 环形引用,如图上main_a指向的对象和main_b指向的对象,它们的成员相互引用对方,这个也是 引用计数方法的重大缺陷。
        解决方法也是有的,就是 在设计类的时候,时刻关注这些环形引用的类,有些成员就用SharedPtr,有些就直接上 裸指针。Boost的智能指针好就好在,它可以不上裸指针,它可以上weak_ptr,这样进一步保护,防止裸指针乱来。
        个人很期待,Ogre将来会有超越智能指针的垃圾回收方案。

参考:

http://blog.csdn.net/hunter8777/article/details/6202339
http://www.twinklingstar.cn/2013/1106/ogre-memory-alloc/
http://www.cnblogs.com/vamei/archive/2013/04/28/3048353.html
http://www.cnblogs.com/wpcockroach/archive/2012/05/10/2493564.html

猜你喜欢

转载自blog.csdn.net/linsoft1994/article/details/51477055
1.9