STL stack allocate

游戏编程精粹3提供了一份栈分配器源代码:

#include <memory>
#include <limits>

template <typename T>
class StackAlloc
{
public:

    // Typedefs
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;

    // Constructors
    StackAlloc() throw()
    :
        mpStack( NULL ),
        mBytesAllocated( 0 ),
        mMaxBytes( 0 )
    {
    }

    StackAlloc( unsigned char* pStack, size_t nMaxBytes ) throw()
    :
        mpStack( pStack ),
        mBytesAllocated( 0 ),
        mMaxBytes( nMaxBytes )
    {
    }

    StackAlloc( const StackAlloc& sa ) throw()
    :
        mpStack( sa.mpStack ),
        mBytesAllocated( 0 ),
        mMaxBytes( sa.mMaxBytes )
    {
        // Copying the "stack" resets mBytesAllocated to zero
    }

#if _MSC_VER >= 1400 // VC 7 can't handle template members
    template <typename U>
    StackAlloc( const StackAlloc<U>& sa ) throw()
    :
        mpStack( sa.mpStack ),
        mBytesAllocated( 0 ),
        mMaxBytes( sa.mMaxBytes )
    {
        // Copying the "stack" resets mBytesAllocated to zero
    }
#endif

    StackAlloc& operator=( const StackAlloc& sa )
    {
        // Copying the "stack" resets mBytesAllocated to zero
        mpStack = sa.mpStack;
        mBytesAllocated = 0;
        mMaxBytes = sa.mMaxBytes;
        return *this;
    }

    // Destructor
    ~StackAlloc() throw()
    {
    }

    // Utility functions
    pointer address( reference r ) const
    {
        return &r;
    }

    const_pointer address( const_reference c ) const
    {
        return &c;
    }

    size_type max_size() const
    {
        return std::numeric_limits<size_t>::max() / sizeof(T);
    }

    // In-place construction
    void construct( pointer p, const_reference c )
    {
        // placement new operator
        new( reinterpret_cast<void*>(p) ) T(c);
    }

    // In-place destruction
    void destroy( pointer p )
    {
        // call destructor directly
        (p)->~T();
    }

    // Rebind to allocators of other types
    template <typename U>
    struct rebind
    {
        typedef StackAlloc<U> other;
    };

    // Allocate raw memory
    pointer allocate( size_type n, const void* = NULL )
    {
        void* pRaw = mpStack + mBytesAllocated;
        mBytesAllocated += ( n * sizeof(T) );

        if( mBytesAllocated+1 > mMaxBytes )
            throw std::bad_alloc();

        return pointer(pRaw);
    }

    // Free raw memory.
    // Note that C++ standard defines this function as
    // deallocate( pointer p, size_type). Because Visual C++ 6.0
    // compiler doesn't support template rebind, Dinkumware uses
    // void* hack.
    void deallocate( void*, size_type )
    {
        // No need to free stack memory
    }

    // Non-standard Dinkumware hack for Visual C++ 6.0 compiler.
    // VC 6 doesn't support template rebind.
    char* _Charalloc( size_type n )
    {
        return reinterpret_cast<char*>( allocate( n, NULL ) );
    }

    // Required for global comparison functions
    unsigned char* GetStack() const
    {
        return mpStack;
    }

private:

    unsigned char* mpStack;
    size_t         mBytesAllocated;
    size_t         mMaxBytes;

}; // end of StackAlloc

// Comparison
template <typename T1>
bool operator==( const StackAlloc<T1>& lhs, const StackAlloc<T1>& rhs) throw()
{
    return lhs.GetStack() == rhs.GetStack();
}

template <typename T1>
bool operator!=( const StackAlloc<T1>& lhs, const StackAlloc<T1>& rhs) throw()
{
    return lhs.GetStack() != rhs.GetStack();
}
View Code

测试发现在VS2012下编译运行,栈分配器在释放内存时出错。

原因是当用户提供自定义的分配器时,VS会保存一个分配策略对象来管理计数神马的。
部分VS源代码如下:

    _Vector_alloc(const _Alty& _Al = _Alty())
        : _Alval(_Al)
        {    // construct allocator from _Al
        _Alloc_proxy();
        }
//-------------------------------------------------------------
    void _Alloc_proxy()
        {    // construct proxy from _Alval
        typename _Alloc::template rebind<_Container_proxy>::other
            _Alproxy(_Alval);
        this->_Myproxy = _Alproxy.allocate(1);
        _Alproxy.construct(this->_Myproxy, _Container_proxy());
        this->_Myproxy->_Mycont = this;
        }
View Code

vector的栈分配器是_Alval,也就是用户提供的栈分配器。

分配策略对象_Myproxy占用的内存也是通过_Alval申请的。仅仅是这样也许不会出现神马问题,可是_Myproxy是通过调用template <typename U> StackAlloc( const StackAlloc<U>& sa )重绑定函数从_Alval获取了一个新的分配器对象_Alproxy。此时_Alval与_Alproxy其实是共用一段内存的,而彼此不知道对方的存在。导致_Myproxy使用的内存在vector插入元素时被覆盖。当vector释放内存或者迁移内存时需要释放掉_Myproxy的内存时程序就崩溃了。

实际上游戏编程精粹3提供的stack allocate在语义上就存在问题,mpstack不应该是值语义的,因为stack allocate实际上即没有真正向系统申请内存,也并没有真正的释放内存,而只是代为管理一段原本就存在的内存。因此,mpstack应该是引用语义的。

现将本人修改后的代码献上:

PS:只在VS2012下测试了vector与list容器。

  1         template<typename T>
  2         class stack_alloc
  3         {
  4         public:
  5             typedef size_t    size_type;
  6             typedef ptrdiff_t difference_type;
  7             typedef T*        pointer;
  8             typedef const T*  const_pointer;
  9             typedef T&        reference;
 10             typedef const T&  const_reference;
 11             typedef T         value_type;
 12 
 13         public:
 14             stack_alloc() throw()
 15                 : m_begin(NULL)
 16                 , m_cur(m_begin)
 17                 , m_max_bytes(0)
 18             {
 19             }
 20 
 21             stack_alloc(uchar* pstack, size_t max_bytes) throw()
 22                 : m_begin(pstack)
 23                 , m_cur(m_begin)
 24                 , m_max_bytes(max_bytes)
 25             {
 26             }
 27 
 28             stack_alloc(const stack_alloc& sa) throw()
 29                 : m_begin(sa.m_begin)
 30                 , m_cur(sa.m_cur)
 31                 , m_max_bytes(sa.m_max_bytes)
 32             {
 33             }
 34 
 35 #if _MSC_VER >= 1400 // VC 7 can't handle template members
 36             template <typename U>
 37             stack_alloc(const stack_alloc<U>& sa) throw()
 38                 : m_begin(sa.m_begin)
 39                 , m_cur(sa.m_cur)
 40                 , m_max_bytes(sa.m_max_bytes)
 41             {
 42             }
 43 #endif
 44 
 45             stack_alloc& operator=( const stack_alloc& rhs )
 46             {
 47                 return *this;
 48             }
 49 
 50             ~stack_alloc() throw()
 51             {
 52             }
 53 
 54         public:
 55             // Utility functions
 56             pointer address( reference r ) const
 57             {
 58                 return &r;
 59             }
 60 
 61             const_pointer address( const_reference c ) const
 62             {
 63                 return &c;
 64             }
 65 
 66             size_type max_size() const
 67             {
 68                 return m_max_bytes/sizeof(T);
 69             }
 70 
 71             // In-place construction
 72             void construct( pointer p, const_reference c )
 73             {
 74                 new( reinterpret_cast<void*>(p) ) T(c);
 75             }
 76 
 77             // In-place destruction
 78             void destroy( pointer p )
 79             {
 80                 (p)->~T();
 81             }
 82 
 83             // Rebind to allocators of other types
 84             template <typename U>
 85             struct rebind
 86             {
 87                 typedef stack_alloc<U> other;
 88             };
 89 
 90             // Allocate raw memory
 91             pointer allocate( size_type n, const void* = NULL )
 92             {
 93                 void* praw = m_cur;
 94                 m_cur += n*sizeof(T);
 95                 if(m_cur+1>m_begin+m_max_bytes)
 96                 {
 97                     throw std::bad_alloc();
 98                 }
 99 
100                 return pointer(praw);
101             }
102 
103             void deallocate( void* p, size_type n)
104             {                
105             }
106 
107             // Non-standard Dinkumware hack for Visual C++ 6.0 compiler.
108             // VC 6 doesn't support template rebind.
109             char* _charalloc( size_type n )
110             {
111                 return reinterpret_cast<char*>( allocate( n, NULL ) );
112             }
113 
114             unsigned char* get_stack() const
115             {
116                 return m_begin;
117             }
118 
119             uchar*&            m_cur;
120             size_t            m_max_bytes;
121             uchar*            m_begin;
122         };
123 
124         template <typename T>
125         bool operator==( const stack_alloc<T>& lhs, const stack_alloc<T>& rhs) throw()
126         {
127             return lhs.get_stack() == rhs.get_stack();
128         }
129 
130         template <typename T>
131         bool operator!=( const stack_alloc<T>& lhs, const stack_alloc<T>& rhs) throw()
132         {
133             return lhs.get_stack() != rhs.get_stack();
134         }
View Code

 

posted @ 2013-06-16 04:40  traits  阅读(561)  评论(0编辑  收藏  举报