天行健 君子当自强而不息

DXUT源码分析 ---- 类CGrowableArray

CGrowableArray是DXUT实现的一个可自动增长的模板类数组,类似于STL里的vector,该类的实现在DXUTmisc.h里。


首先来看看它的定义部分:

//--------------------------------------------------------------------------------------
// A growable array
//--------------------------------------------------------------------------------------
template< typename TYPE >
class CGrowableArray
{
public:
    CGrowableArray()  
    { 
        m_pData        
= NULL; 
        m_nSize        
= 0
        m_nMaxSize    
= 0
    }

    CGrowableArray( 
const CGrowableArray<TYPE>& a ) 
    { 
        
forint i=0; i < a.m_nSize; i++ ) 
            Add( a.m_pData[i] ); 
    }

    
~CGrowableArray() 
    { 
        RemoveAll(); 
    }

    
const TYPE& operator[]( int nIndex ) const  { return GetAt( nIndex ); }
    TYPE
& operator[]( int nIndex )                { return GetAt( nIndex ); }
   
    CGrowableArray
& operator=const CGrowableArray<TYPE>& a ) 
    { 
        
ifthis == &a ) 
            
return *this
        
        RemoveAll(); 
        
        
forint i=0; i < a.m_nSize; i++ ) 
            Add( a.m_pData[i] ); 
        
        
return *this
    }

    HRESULT SetSize( 
int nNewMaxSize );
    HRESULT Add( 
const TYPE& value );
    HRESULT Insert( 
int nIndex, const TYPE& value );
    HRESULT SetAt( 
int nIndex, const TYPE& value );

    TYPE
&   GetAt( int nIndex ) 
    { 
        assert( nIndex 
>= 0 && nIndex < m_nSize ); 
        
return m_pData[nIndex]; 
    }

    
int     GetSize() const                    { return m_nSize; }
    TYPE
*   GetData()                        { return m_pData; }
    
bool    Contains( const TYPE& value )    { return ( -1 != IndexOf( value ) ); }

    
int IndexOf( const TYPE& value ) 
    { 
        
return ( m_nSize > 0 ) ? IndexOf( value, 0, m_nSize ) : -1
    }

    
int IndexOf( const TYPE& value, int iStart ) 
    { 
        
return IndexOf( value, iStart, m_nSize - iStart ); 
    }

    
int IndexOf( const TYPE& value, int nIndex, int nNumElements );

    
int LastIndexOf( const TYPE& value ) 
    { 
        
return ( m_nSize > 0 ) ? LastIndexOf( value, m_nSize-1, m_nSize ) : -1
    }

    
int LastIndexOf( const TYPE& value, int nIndex ) 
    { 
        
return LastIndexOf( value, nIndex, nIndex+1 ); 
    }

    
int LastIndexOf( const TYPE& value, int nIndex, int nNumElements );

    HRESULT Remove( 
int nIndex );
    
void    RemoveAll() { SetSize(0); }

protected:
    TYPE
* m_pData;      // the actual array of data
    int m_nSize;        // # of elements (upperBound - 1)
    int m_nMaxSize;     // max allocated

    HRESULT SetSizeInternal( 
int nNewMaxSize );  // This version doesn't call constructor or destructor.
};

 

 

SetSizeInternal() 分析

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::SetSizeInternal( int nNewMaxSize )
{
    
if( nNewMaxSize < 0 )
    {
        assert( 
false );
        
return E_INVALIDARG;
    }

    
if( nNewMaxSize == 0 )
    {
        
// Shrink to 0 size & cleanup
        if( m_pData )
        {
            free( m_pData );
            m_pData 
= NULL;
        }

        m_nMaxSize 
= 0;
        m_nSize       
= 0;
    }
    
else if( m_pData == NULL || nNewMaxSize > m_nMaxSize )
    {
        
// Grow array
        int nGrowBy = ( m_nMaxSize == 0 ) ? 16 : m_nMaxSize;
        nNewMaxSize 
= __max( nNewMaxSize, m_nMaxSize + nGrowBy );

        TYPE
* pDataNew = (TYPE*) realloc( m_pData, nNewMaxSize * sizeof(TYPE) );
        
if( pDataNew == NULL )
            
return E_OUTOFMEMORY;

        m_pData       
= pDataNew;
        m_nMaxSize 
= nNewMaxSize;
    }

    
return S_OK;
}

SetSizeInternal()是protected类型的方法,主要供其他方法内部调用,函数首先检查nNewMaxSize是否合法,如果nNewMaxSize为0则释放分配的内存,如果m_pData为NULL或者指定的nNewMaxSize大于原先分配的内存大小m_nMaxSize,则重新分配内存。

// Grow array
int nGrowBy = ( m_nMaxSize == 0 ) ? 16 : m_nMaxSize;  

如果m_nMaxSize为0,意味着没有为m_nMaxSize指定大小,则将nGrowBy赋值为16,即增加的内存大小为16 * sizeof(TYPE);若指定了m_nMaxSize,则增加的大小为m_nMaxSize * sizeof(TYPE),即将分配内存调整为原来的两倍。

nNewMaxSize = __max( nNewMaxSize, m_nMaxSize + nGrowBy );

#define __max(a,b) (((a) > (b)) ? (a) : (b))

nNewMaxSize在新指定分配内存大小与自动增长的内存m_nMaxSize + nGrowBy 中取一个较大的值。

TYPE* pDataNew = (TYPE*) realloc( m_pData, nNewMaxSize * sizeof(TYPE) );
if( pDataNew == NULL )
        return E_OUTOFMEMORY;

m_pData       = pDataNew;
m_nMaxSize = nNewMaxSize;

pDataNew为指向重新分配内存的指针,m_pData = pDataNew将m_pData指向新分配的内存,m_nMaxSize = nNewMaxSize更新分配后内存的最大尺寸。

函数realloc()的声明如下:

Reallocate memory blocks.

 
void *realloc(
void *memblock,
size_t size
);

Parameters

memblock
Pointer to previously allocated memory block.
size
New size in bytes.

Return Value

realloc returns a void pointer to the reallocated (and possibly moved) memory block.

If there is not enough available memory to expand the block to the given size, the original block is left unchanged, and NULL is returned.

If size is zero, then the block pointed to by memblock is freed; the return value is NULL, and memblock is left pointing at a freed block.

The return value points to a storage space that is guaranteed to be suitably aligned for storage of any type of object. To get a pointer to a type other than void, use a type cast on the return value.

Remarks

The realloc function changes the size of an allocated memory block. The memblock argument points to the beginning of the memory block. If memblock is NULL, realloc behaves the same way as malloc and allocates a new block of size bytes. If memblock is not NULL, it should be a pointer returned by a previous call to calloc, malloc, or realloc.

The size argument gives the new size of the block, in bytes. The contents of the block are unchanged up to the shorter of the new and old sizes, although the new block can be in a different location. Because the new block can be in a new memory location, the pointer returned by realloc is not guaranteed to be the pointer passed through the memblock argument.

In Visual C++ 2005, realloc sets errno to ENOMEM if the memory allocation fails or if the amount of memory requested exceeds _HEAP_MAXREQ. For information on this and other error codes, see errno, _doserrno, _sys_errlist, and _sys_nerr.

realloc calls malloc in order to use the C++ _set_new_mode function to set the new handler mode. The new handler mode indicates whether, on failure, malloc is to call the new handler routine as set by _set_new_handler. By default, malloc does not call the new handler routine on failure to allocate memory. You can override this default behavior so that, when realloc fails to allocate memory, malloc calls the new handler routine in the same way that the new operator does when it fails for the same reason. To override the default, call

_set_new_mode(1)

early in ones program, or link with NEWMODE.OBJ (see Link Options).

When the application is linked with a debug version of the C run-time libraries, realloc resolves to _realloc_dbg. For more information about how the heap is managed during the debugging process, see The CRT Debug Heap.

realloc is marked __declspec(noalias) and __declspec(restrict), meaning that the function is guaranteed not to modify global variables, and that the pointer returned is not aliased. For more information, see noalias and restrict.

Requirements

Routine Required header Compatibility
realloc <stdlib.h> and <malloc.h> ANSI, Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition, Windows NT 4.0, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows Server 2003

For additional compatibility information, see Compatibility in the Introduction.

Example

// crt_realloc.c
// This program allocates a block of memory for buffer and then uses _msize to display the size of that
// block. Next, it uses realloc to expand the amount of memory used by buffer and then calls _msize again to
// display the new amount of memory allocated to buffer.

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

int main( void )
{
long *buffer, *oldbuffer;
size_t size;

if( (buffer = (long *)malloc( 1000 * sizeof( long ) )) == NULL )
exit( 1 );

size = _msize( buffer );
printf( "Size of block after malloc of 1000 longs: %u\n", size );

// Reallocate and show new size:
oldbuffer = buffer; // save pointer in case realloc fails
if( (buffer = realloc( buffer, size + (1000 * sizeof( long )) ))
== NULL )
{
free( oldbuffer ); // free original block
exit( 1 );
}
size = _msize( buffer );
printf( "Size of block after realloc of 1000 more longs: %u\n",
size );

free( buffer );
exit( 0 );
}

Output

Size of block after malloc of 1000 longs: 4000
Size of block after realloc of 1000 more longs: 8000

 

SetSize()分析

原代码:

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::SetSize( int nNewMaxSize )
{
    
int nOldSize = m_nSize;

    
if( nOldSize > nNewMaxSize )
    {
        
// Removing elements. Call destructor.
        forint i = nNewMaxSize; i < nOldSize; ++i )
            m_pData[i].
~TYPE();
    }

    
// Adjust buffer.  Note that there's no need to check for error
    
// since if it happens, nOldSize == nNewMaxSize will be true.
    HRESULT hr = SetSizeInternal( nNewMaxSize );

    
if( nOldSize < nNewMaxSize )
    {
        
// Adding elements. Call constructor.
        forint i = nOldSize; i < nNewMaxSize; ++i )
            ::
new (&m_pData[i]) TYPE;
    }

    
return hr;
}

个人觉得这代码写的有些问题,作者说如果调用SetSizeInternal()失败,则nOldSize == nNewMaxSize必成立,但实际上我们查看SetSizeInternal()的代码发现:

TYPE* pDataNew = (TYPE*) realloc( m_pData, nNewMaxSize * sizeof(TYPE) );
 if( pDataNew == NULL )
       return E_OUTOFMEMORY;

也就是说当realloc()失败的时候SetSizeInternal()调用会失败,这时nOldSize为m_nSize,它不会恒等于nNewMaxSize,于是我将上面的代码修改为:

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::SetSize( int nNewMaxSize )
{
    
int nOldSize = m_nSize;

    
if( nOldSize > nNewMaxSize )
    {
        
// Removing elements. Call destructor.
        forint i = nNewMaxSize; i < nOldSize; ++i )
            m_pData[i].
~TYPE();
    }

    
// Adjust buffer.  
    HRESULT hr = SetSizeInternal( nNewMaxSize );

    
if(FAILED(hr))
        
return hr;

    
if( nOldSize < nNewMaxSize )
    {
        
// Adding elements. Call constructor.
        forint i = nOldSize; i < nNewMaxSize; ++i )
            ::
new (&m_pData[i]) TYPE;
    }

    
return S_OK;
}

函数首先将原先内存中的已赋值的元素个数保存为nOldSize,接下来的代码:

    if( nOldSize > nNewMaxSize )
    {
        // Removing elements. Call destructor.
        for( int i = nNewMaxSize; i < nOldSize; ++i )
            m_pData[i].~TYPE();
    }

检查新指定的内存大小是否小于已分配内存中已赋值元素的个数,如果是则显式调用各元素的析构函数释放资源,如下图所示:


接着调用SetSizeInternal()重新分配大小,失败则返回:

    // Adjust buffer.  
    HRESULT hr = SetSizeInternal( nNewMaxSize );

    if(FAILED(hr))
        return hr;

如果nNewMaxSize大于nOldSize,则调用构造函数初始化元素的数据,如下图所示:


    if( nOldSize < nNewMaxSize )
    {
        // Adding elements. Call constructor.
        for( int i = nOldSize; i < nNewMaxSize; ++i )
            ::new (&m_pData[i]) TYPE;
    }

这里对显式调用构造函数和析构函数做一些说明,之所以显式调用,是因为new没有renew,而malloc和calloc有realloc,调用realloc可以避免频繁调用malloc()和free()【或者new和delete】造成的性能损失,而realloc()不会自动调用构造和析构函数,所以需要显式调用。


Add()分析:

源码:

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::Add( const TYPE& value )
{
    HRESULT hr;

    
if( FAILED( hr = SetSizeInternal( m_nSize + 1 ) ) )
        
return hr;

    
// Construct the new element
    ::new (&m_pData[m_nSize]) TYPE;
    
    m_pData[m_nSize] 
= value;
    
++m_nSize;

    
return S_OK;
}

代码相当明了,首先调用SetSizeInternal()分配大小,然后调用构造函数,给m_pData对应位置的元素赋值,接着增加m_nSize的大小。
需要说明的是SetSizeInternal()并不会每调用一次Add()就重新分配内存,只有当指定的元素个数超过了m_nMaxSize的时候才会重新分配内存。

 

Insert()分析:

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::Insert( int nIndex, const TYPE& value )
{
    HRESULT hr;

    
// Validate index
    if( nIndex < 0 || nIndex > m_nSize )
    {
        assert( 
false );
        
return E_INVALIDARG;
    }

    
// Prepare the buffer
    if( FAILED( hr = SetSizeInternal( m_nSize + 1 ) ) )
        
return hr;

    
// Shift the array
    MoveMemory( &m_pData[nIndex+1], &m_pData[nIndex], sizeof(TYPE) * (m_nSize - nIndex) );

    
// Construct the new element
    ::new (&m_pData[nIndex]) TYPE;

    
// Set the value and increase the size
    m_pData[nIndex] = value;
    
++m_nSize;

    
return S_OK;
}

Insert()在指定位置nIndex插入一个元素,函数通过MoveMemory()来移动内存,它的定义如下:

#define MoveMemory RtlMoveMemory

#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length))

接着调用构造函数,赋值,增加m_nSize的大小。

 

SetAt():

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::SetAt( int nIndex, const TYPE& value )
{
    
// Validate arguments
    if( nIndex < 0 || nIndex >= m_nSize )
    {
        assert( 
false );
        
return E_INVALIDARG;
    }

    m_pData[nIndex] 
= value;
    
return S_OK;
}


IndexOf():
 
//--------------------------------------------------------------------------------------
// Searches for the specified value and returns the index of the first occurrence
// within the section of the data array that extends from iStart and contains the 
// specified number of elements. Returns -1 if value is not found within the given 
// section.
//--------------------------------------------------------------------------------------
template< typename TYPE >
int CGrowableArray<TYPE>::IndexOf( const TYPE& value, int iStart, int nNumElements )
{
    
// Validate arguments
    if( iStart < 0 || iStart >= m_nSize || nNumElements < 0 || iStart + nNumElements > m_nSize )
    {
        assert( 
false );
        
return -1;
    }

    
// Search
    forint i = iStart; i < (iStart + nNumElements); i++ )
    {
        
if( value == m_pData[i] )
            
return i;
    }

    
// Not found
    return -1;
}

 

LastIndexOf():

//--------------------------------------------------------------------------------------
// Searches for the specified value and returns the index of the last occurrence
// within the section of the data array that contains the specified number of elements
// and ends at iEnd. Returns -1 if value is not found within the given section.
//--------------------------------------------------------------------------------------
template< typename TYPE >
int CGrowableArray<TYPE>::LastIndexOf( const TYPE& value, int iEnd, int nNumElements )
{
    
// Validate arguments
    if( iEnd < 0 || iEnd >= m_nSize || nNumElements < 0 || iEnd - nNumElements < 0 )
    {
        assert( 
false );
        
return -1;
    }

    
// Search
    forint i = iEnd; i > (iEnd - nNumElements); i-- )
    {
        
if( value == m_pData[i] )
            
return i;
    }

    
// Not found
    return -1;
}

 

Remove():

template< typename TYPE >
HRESULT CGrowableArray
<TYPE>::Remove( int nIndex )
{
    
if( nIndex < 0 || nIndex >= m_nSize )
    {
        assert( 
false );
        
return E_INVALIDARG;
    }

    
// Destruct the element to be removed
    m_pData[nIndex].~TYPE();

    
// Compact the array and decrease the size
    MoveMemory( &m_pData[nIndex], &m_pData[nIndex+1], sizeof(TYPE) * (m_nSize - (nIndex+1)) );
    
--m_nSize;

    
return S_OK;
}

 

posted on 2008-05-18 14:05 lovedday 阅读(2467) 评论(0)  编辑 收藏 引用 所属分类: ■ DXUT Research


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论