CGrowableArray是DXUT實(shí)現(xiàn)的一個(gè)可自動(dòng)增長的模板類數(shù)組,類似于STL里的vector,該類的實(shí)現(xiàn)在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 )
{
for( int 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 )
{
if( this == &a )
return *this;
RemoveAll();
for( int 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類型的方法,主要供其他方法內(nèi)部調(diào)用,函數(shù)首先檢查nNewMaxSize是否合法,如果nNewMaxSize為0則釋放分配的內(nèi)存,如果m_pData為NULL或者指定的nNewMaxSize大于原先分配的內(nèi)存大小m_nMaxSize,則重新分配內(nèi)存。
// Grow array
int nGrowBy = ( m_nMaxSize == 0 ) ? 16 : m_nMaxSize;
如果m_nMaxSize為0,意味著沒有為m_nMaxSize指定大小,則將nGrowBy賦值為16,即增加的內(nèi)存大小為16
* sizeof(TYPE);若指定了m_nMaxSize,則增加的大小為m_nMaxSize
* sizeof(TYPE),即將分配內(nèi)存調(diào)整為原來的兩倍。
nNewMaxSize = __max( nNewMaxSize, m_nMaxSize + nGrowBy );
#define __max(a,b) (((a) > (b)) ? (a) : (b))
nNewMaxSize在新指定分配內(nèi)存大小與自動(dòng)增長的內(nèi)存m_nMaxSize + nGrowBy 中取一個(gè)較大的值。
TYPE* pDataNew = (TYPE*) realloc( m_pData, nNewMaxSize * sizeof(TYPE) );
if( pDataNew == NULL )
return E_OUTOFMEMORY;
m_pData = pDataNew;
m_nMaxSize = nNewMaxSize;
pDataNew為指向重新分配內(nèi)存的指針,m_pData = pDataNew將m_pData指向新分配的內(nèi)存,m_nMaxSize = nNewMaxSize更新分配后內(nèi)存的最大尺寸。
函數(shù)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
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.
for( int 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.
for( int i = nOldSize; i < nNewMaxSize; ++i )
::new (&m_pData[i]) TYPE;
}
return hr;
}
個(gè)人覺得這代碼寫的有些問題,作者說如果調(diào)用SetSizeInternal()失敗,則nOldSize
== nNewMaxSize必成立,但實(shí)際上我們查看SetSizeInternal()的代碼發(fā)現(xiàn):
TYPE* pDataNew = (TYPE*) realloc( m_pData, nNewMaxSize * sizeof(TYPE) );
if( pDataNew == NULL )
return E_OUTOFMEMORY;
也就是說當(dāng)realloc()失敗的時(shí)候SetSizeInternal()調(diào)用會(huì)失敗,這時(shí)nOldSize為m_nSize,它不會(huì)恒等于nNewMaxSize,于是我將上面的代碼修改為:
template< typename TYPE >
HRESULT CGrowableArray<TYPE>::SetSize( int nNewMaxSize )
{
int nOldSize = m_nSize;
if( nOldSize > nNewMaxSize )
{
// Removing elements. Call destructor.
for( int 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.
for( int i = nOldSize; i < nNewMaxSize; ++i )
::new (&m_pData[i]) TYPE;
}
return S_OK;
}
函數(shù)首先將原先內(nèi)存中的已賦值的元素個(gè)數(shù)保存為nOldSize,接下來的代碼:
if( nOldSize > nNewMaxSize )
{
// Removing elements. Call destructor.
for( int i = nNewMaxSize; i < nOldSize; ++i )
m_pData[i].~TYPE();
}
檢查新指定的內(nèi)存大小是否小于已分配內(nèi)存中已賦值元素的個(gè)數(shù),如果是則顯式調(diào)用各元素的析構(gòu)函數(shù)釋放資源,如下圖所示:
接著調(diào)用SetSizeInternal()重新分配大小,失敗則返回:
// Adjust buffer.
HRESULT hr = SetSizeInternal( nNewMaxSize );
if(FAILED(hr))
return hr;
如果nNewMaxSize大于nOldSize,則調(diào)用構(gòu)造函數(shù)初始化元素的數(shù)據(jù),如下圖所示:
if( nOldSize < nNewMaxSize )
{
// Adding elements. Call constructor.
for( int i = nOldSize; i < nNewMaxSize; ++i )
::new (&m_pData[i]) TYPE;
}
這里對(duì)顯式調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)做一些說明,之所以顯式調(diào)用,是因?yàn)閚ew沒有renew,而malloc和calloc有realloc,調(diào)用realloc可以避免頻繁調(diào)用malloc()和free()【或者new和delete】造成的性能損失,而realloc()不會(huì)自動(dòng)調(diào)用構(gòu)造和析構(gòu)函數(shù),所以需要顯式調(diào)用。
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;
}
代碼相當(dāng)明了,首先調(diào)用SetSizeInternal()分配大小,然后調(diào)用構(gòu)造函數(shù),給m_pData對(duì)應(yīng)位置的元素賦值,接著增加m_nSize的大小。
需要說明的是SetSizeInternal()并不會(huì)每調(diào)用一次Add()就重新分配內(nèi)存,只有當(dāng)指定的元素個(gè)數(shù)超過了m_nMaxSize的時(shí)候才會(huì)重新分配內(nèi)存。
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插入一個(gè)元素,函數(shù)通過MoveMemory()來移動(dòng)內(nèi)存,它的定義如下:
#define MoveMemory RtlMoveMemory
#define
RtlMoveMemory(Destination,Source,Length)
memmove((Destination),(Source),(Length))
接著調(diào)用構(gòu)造函數(shù),賦值,增加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
for( int 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
for( int 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;
}