I made a class that encapsulates the common pattern of keeping an array contiguous when an element is removed by swapping the last element into its place.
A few specific things I'm wondering about:
- Is it unsafe for non-POD types (because of the memcpy)?
- Is there a more efficient way to add a new object (so it doesn't have to be copied)?
- Should I worry about code bloat from capacity being a template parameter (N)?
This was not written with C++11 in mind, except for a few little features supported by Visual Studio 2010 (like auto).
Edit for clarification: I'm using aligned memory instead of a simple array of T so that I have control over when the objects in the array are created and destroyed. I'm using a memcpy to avoid calling a constructor every time something is swapped.
SwapArray.h:
#ifndef INCLUDE_GUARD_78F4FE5C_3E57_44C4_9E42_2574051D5A18
#define INCLUDE_GUARD_78F4FE5C_3E57_44C4_9E42_2574051D5A18
#include <boost/type_traits/alignment_of.hpp>
#include <boost/type_traits/aligned_storage.hpp>
namespace ktc
{
// an array that keeps all its elements contiguous by swapping removed elements
// with the last element of the array.
//
// add and remove are both O(1).
template <typename T, unsigned int N>
class SwapArray
{
public:
SwapArray();
~SwapArray();
// number of elements currently in the array.
unsigned int size() const;
// max size.
unsigned int capacity() const;
// add a value to the end.
void add( const T& val );
// remove the value at this index.
void rem( unsigned int i );
// remove all.
void clear();
T& operator[]( unsigned int i );
const T& operator[]( unsigned int i ) const;
template <typename Compare>
void sort( Compare compare );
private:
typedef
typename boost::aligned_storage
< N * sizeof(T),
boost::alignment_of<T>::value
>::type
AlignedMem;
// aligned for type T.
AlignedMem m_alignedMem;
unsigned int m_size;
// return the data for the i-th element.
char* memAt( unsigned int i );
const char* memAt( unsigned int i ) const;
};
} // namespace ktc
#include "SwapArray.hpp"
#endif
SwapArray.hpp:
#include <algorithm>
#include <cstdio>
#include <new>
#include "ktcAssert.h"
namespace ktc
{
template <typename T, unsigned int N>
SwapArray<T,N>::SwapArray()
{
static_assert( N > 0, "SwapArray can't have capacity 0" );
m_size = 0;
}
template <typename T, unsigned int N>
SwapArray<T,N>::~SwapArray()
{
clear();
}
template <typename T, unsigned int N>
void SwapArray<T,N>::clear()
{
for( unsigned int i = 0; i < m_size; i++ )
(*this)[i].~T();
m_size = 0;
}
template <typename T, unsigned int N>
unsigned int SwapArray<T,N>::size() const
{
return m_size;
}
template <typename T, unsigned int N>
unsigned int SwapArray<T,N>::capacity() const
{
return N;
}
template <typename T, unsigned int N>
void SwapArray<T,N>::add( const T& val )
{
ktcAssert( m_size < N );
// copy it onto the end.
new( memAt(m_size) ) T(val);
m_size++;
}
template <typename T, unsigned int N>
void SwapArray<T,N>::rem( unsigned int i )
{
ktcAssert( m_size > 0 );
ktcAssert( i < m_size );
// remove the element.
char* r = memAt(i);
T* rt = (T*) (r);
rt->~T();
// if it wasn't the last element that was removed, swap the last element
// into its spot.
if( i < m_size - 1 )
{
char* last = memAt( m_size - 1 );
ktcAssert( last != r );
memcpy( r, last, sizeof(T) );
}
m_size--;
}
template <typename T, unsigned int N>
T& SwapArray<T,N>::operator[]( unsigned int i )
{
const auto* ct = static_cast<const SwapArray<T,N>*>( this );
return const_cast<T&>( ct->operator[](i) );
}
template <typename T, unsigned int N>
const T& SwapArray<T,N>::operator[]( unsigned int i ) const
{
ktcAssert( i < m_size );
return *( (T*) memAt(i) );
}
template <typename T, unsigned int N>
char* SwapArray<T,N>::memAt( unsigned int i )
{
auto ct = static_cast<const SwapArray<T,N>*> (this);
return const_cast<char*> ( ct->memAt(i) );
}
template <typename T, unsigned int N>
const char* SwapArray<T,N>::memAt(unsigned int i) const
{
ktcAssert( i < N );
return static_cast<const char*>( m_alignedMem.address() ) + (i * sizeof(T));
}
template <typename T, unsigned int N>
template <typename Compare>
void SwapArray<T,N>::sort( Compare compare )
{
if( size() < 2 )
return;
std::sort( &(*this)[0], &(*this)[size() - 1], compare );
}
} // namespace ktc