I've been coding a helper container template to contain tiles in 2D games. What I figured out I would like when writing code for my games is forgetting all resize stuff, allowing negative indexes (for proceduraly generated worlds, that I hope to achieve in the future) and returning a default value (and not some error) if the index is out of bounds.
The most important part of the code is the operator[]
overload.
Here is what I wrote (it's quite a chunk !):
#ifndef AUTOARRAY_H
#define AUTOARRAY_H
#include <vector>
#ifdef DO_DEBUG
#include <iostream>
#endif
//#define AUTOARRAY_ALLOW_GET //allows to acces real stl container directly
//#define AUTOARRAY_ALLOW_RSIZE //allows to get the real, cached size of the container
//#define AUTOARRAY_2D //include auto2DArray typedef.
//#define DO_DEBUG //do debug lines, such as prints and logs
namespace yk
{
template<typename T>
class autoArray
{
private:
typedef std::vector<T> array_type;
typedef typename array_type::iterator iterator_t;
typedef typename array_type::const_iterator citerator_t;
array_type m_array, m_negArray;
T m_defVal;
size_t m_fakeSize;
size_t m_fakeNegSize;
//helper init function
inline void _init( const T& defaultValue, const array_type& initial, const array_type& negInitial )
{
m_defVal = defaultValue;
m_array = initial;
m_fakeSize = initial.size();
m_negArray = negInitial;
m_fakeNegSize = negInitial.size();
}
//cause it didn't work w/o ?
inline bool _greaterThanSize( const int& n ) const
{ return static_cast<size_t>(n) > realSize(); }
inline bool _greaterThanNegSize( const int& n ) const
{ return static_cast<size_t>(n) > realNegSize(); }
/*inline void _resize( const int& index, array_type& ar, size_t& arraySize )
{
//Not doing it yet 'cause the code isn't realy duplicate and I
//can't figure out an intelligent way to do it.
}*/
public:
//default initialiser
inline autoArray()
{ _init( T(), {}, {} ); }
//initialiser with a default value when resizing.
inline autoArray( const T& defaultValue )
{ _init( defaultValue, {}, {} ); }
//initialiser with an initial array
inline autoArray( const array_type& initial )
{ _init( T(), initial, {} ); }
//initialiser with an initial array and negArray
inline autoArray( const array_type& initial, const array_type& negInitial )
{ _init( T(), initial, negInitial ); }
//initialiser with an initial array and default resize value
inline autoArray( const T& defaultValue, const array_type& initial )
{ _init( defaultValue, initial, {} ); }
//initialiser with an initial array, negArray and default resize value
inline autoArray( const T& defaultValue, const array_type& initial, const array_type& negInitial )
{ _init( defaultValue, initial, negInitial ); }
//so you only get the size of the used array, not the cached one.
inline size_t size() const
{ return m_fakeSize; }
inline size_t negSize() const
{ return m_fakeNegSize; }
//if index is too big, resize accordingly and return defVal
inline T& operator[]( const int& index )
{
#ifdef DO_DEBUG
std::cout << "index: " << index << " ";
#endif
if( index >= 0 )
{
//if the index is positive (or eq. to 0)
//use the positive array
#ifdef DO_DEBUG
std::cout << "index is positive" << std::endl;
#endif
if( _greaterThanSize( index + 1 ) )
{
//resize way bigger, cause resizing is slow.
//this speeds things up a bit by being overeager.
#ifdef DO_DEBUG
std::cout << "resizing pos array" << std::endl;
#endif
m_array.resize( ( index + 1 ) * 2, m_defVal );
}
if( static_cast<size_t>(index) + 1 > m_fakeSize )
{
//set fake size to the biggest index accessed,
//so the user doesn't get all the defvals when he iterates through.
#ifdef DO_DEBUG
std::cout << "seting fake size to " << m_fakeSize << std::endl;
#endif
m_fakeSize = static_cast<size_t>(index) + 1;
}
return m_array[index];
} else
{
//if the index is positive (or eq. to 0)
//use the negative array
int realIndex = -index - 1;
//the index of the value in the array starts at 1,
//index 0 being stored in he positive array
#ifdef DO_DEBUG
std::cout << "index is negative" << std::endl;
#endif
if( _greaterThanNegSize( realIndex + 1 ) )
{
//resize way bigger, cause resizing is slow.
//this speeds things up a bit by being overeager.
#ifdef DO_DEBUG
std::cout << "resizing neg array to " << ( realIndex + 1 ) * 2 << std::endl;
#endif
m_negArray.resize( ( realIndex + 1 ) * 2, m_defVal );
}
if( static_cast<size_t>( realIndex + 1 ) > m_fakeNegSize )
{
//set fake size to the biggest index accessed,
//so the user doesn't get all the defvals when he iterates through.
#ifdef DO_DEBUG
std::cout << "seting fake size to " << static_cast<size_t>( realIndex ) + 1 << std::endl;
#endif
m_fakeNegSize = static_cast<size_t>( realIndex ) + 1;
}
#ifdef DO_DEBUG
std::cout << "returning val in neg array at index " << realIndex << std::endl;
std::cout << "real size of array: " << m_negArray.size() << std::endl;
#endif
return m_negArray[realIndex];
}
}
//index operator for situations when the entity is const
inline T operator[]( const int& index ) const
{
/*
//for some reason doesn't work
if( _greaterThanSize(index) or _greaterThanNegSize( -index - 1))
{
#ifdef DO_DEBUG
std::cout << "returning defval (index too small or too big)" << std::endl;
#endif
return m_defVal;
}*/
if( index >= 0 )
{
#ifdef DO_DEBUG
std::cout << "returning at index " << index << " in posarray" << std::endl;
#endif
return m_array[index];
}
else
{
#ifdef DO_DEBUG
std::cout << "returning at index " << index << " in negarray" << std::endl;
std::cout << "(real index : " << -index - 1 << ")" << std::endl;
#endif
return m_negArray[-index - 1];
}
}
//For ranged based loops - to be modified.
//I'll have to define my own type of iterator...
/*
inline iterator_t begin()
{ return m_array.begin(); }
inline citerator_t begin() const
{ return m_array.begin(); }
inline iterator_t end()
{ return m_array.end() - (realSize() - size()); }
inline citerator_t end() const
{ return m_array.end() - (realSize() - size()); }
*/
//for cout
friend inline std::ostream& operator<<( std::ostream& stream, const autoArray<T>& oarray )
{
#ifdef DO_DEBUG
std::cout << "printing from " << -static_cast<int>(oarray.negSize()) << " to " << oarray.size() << std::endl;
#endif
for(int i = -static_cast<int>(oarray.negSize()); i < static_cast<int>(oarray.size()); i++)
{
stream << i << " : " << oarray[i] << std::endl;
}
return stream;
}
virtual ~autoArray() { }
#ifdef AUTOARRAY_ALLOW_GET
inline array_type& get()
{ return m_array; }
inline array_type& getNeg()
{ return m_negArray; }
#endif //AUTOARRAY_ALLOW_GET
//if client doesnt use it, declare it private
#ifdef AUTOARRAY_ALLOW_RSIZE
public:
#else
private:
#endif //AUTOARRAY_ALLOW_RSIZE
inline size_t realSize() const
{ return m_array.size(); }
inline size_t realNegSize() const
{ return m_negArray.size(); }
}; //autoArray class definition
#ifdef AUTOARRAY_2D
template<typename T>
using auto2DArray = autoArray<autoArray<T> >;
template<typename T>
inline auto2DArray<T> makeAuto2DwithDefault(const T& defVal)
{
autoArray<T> a( defVal );
return auto2DArray<T>( a );
}
#endif
} //namespace yk
#endif // AUTOARRAY_H
Couple of things I'm not sure about :
- Is the
DO_DEBUG
macro a good idea ? If not, what alternative should I use? - I used 2 containers, one for the positive and 0 indexes, an other for the negative ones, because shifting all values seemed slow and tedious to me. Does this use significantly more memory?
- The basic aim of this class is to create 2D arrays, but I thought that making a generic version first was a better idea, leading me to the final
typedef
. Is including atypedef
in function of a macro good design? It seems not enough to put in a file. - How could I make this faster? It will possibly contain huge amounts of data in some occasions.