#ifndef DK_ARRAY_H
#define DK_ARRAY_H
The corresponding termination of this switch is found at the
End of the file.
#include <assert.h>
#include <string.h>
#include <new.h>
#include "dk_defs.h"
#include "auto_res.h"
#ifndef HAVE_BUILT_IN_BOOL
enum bool
{
false = 0,
true = 1
};
#endif
#ifdef HAVE_ARRAY_OPERATORS
# define ARRAY_OP_NEW(size) ::operator new[] (size)
# define ARRAY_OP_DELETE(ptr) ::operator delete[] (ptr)
#else
# define ARRAY_OP_NEW(size) ::operator new (size)
# define ARRAY_OP_DELETE(ptr) ::operator delete (ptr)
#endif
Other macros used are:
template <class T>
struct default_array_traits
{
static void *allocate(int size) { return ARRAY_OP_NEW(size * sizeof(T)); }
static void dislocate(int, void *ptr) { ARRAY_OP_DELETE(ptr); }
static void create(T const &proto, void *dst) { new(dst) T(proto); }
static void copy(T const &src, void *dst) { new(dst) T(src); }
static void move(T &src, void *dst) { new(dst) T(src); src.~T(); }
static void destruct(T &obj) { obj.~T(); }
};
template <class T>
struct memcpy_array_traits: public default_array_traits<T>
{
static void move(T &src, void *dst) { memcpy(dst, &src, sizeof(T)); }
};
It was suggested (by Alex Martelli) to provide objects counts to the
functions: this would allow to copy multiple objects with one call
memcpy(). Another advantage could be something like a simple
persistent array: The constructor could just fill the array from some
external source, although I don't know yet how to specify this source.
An option could be a member template (which is unfortunately not
supported by the compilers I have available). I will consider a change
of this aspect when the other parts of the array are ready.
Another suggestion (by Stan Sulsky) wants to make use of the
memcpy_array_traits for built-in types by default. The
implementation is easy: Instead of using
default_array_traits a class array_traits is
derived from default_arrary_traits. For each built-in type
T a specialization for the class array_traits<T>
using the memcpy_array_traits is provided.
I have provided the code for this approach because it also points
into the direction what to do in the case of traits specialized for
certain class (using a "move constructor"). I have made some simple
performance tests, comparing the default_array_traits with
the memcpy_array_traits which revealed that the
default_array_traits were faster on the machine used (SUN
Sparc Station). I will make the program to test the performance and
some results available as soon as possible...).
Below it the implementation. If memcpy() is really faster
than the hand-written loop, HAVE_FAST_MEMCPY can be defined
to use the memcpy_array_traits by default for built-in
types other than pointers.
template <class T>
struct array_traits: public default_array_traits<T>
{
};
#ifdef HAVE_FAST_MEMCPY
struct array_traits<char>: public memcpy_array_traits<char> { };
struct array_traits<unsigned char>: public memcpy_array_traits<unsigned char> { };
struct array_traits<signed char>: public memcpy_array_traits<signed char> { };
struct array_traits<unsigned short>: public memcpy_array_traits<unsigned short> { };
struct array_traits<signed short>: public memcpy_array_traits<signed short> { };
struct array_traits<unsigned int>: public memcpy_array_traits<unsigned int> { };
struct array_traits<signed int>: public memcpy_array_traits<signed int> { };
struct array_traits<unsigned long>: public memcpy_array_traits<unsigned long> { };
struct array_traits<signed long>: public memcpy_array_traits<signed long> { };
struct array_traits<bool>: public memcpy_array_traits<bool> { };
struct array_traits<double>: public memcpy_array_traits<double> { };
struct array_traits<long double>: public memcpy_array_traits<long double> { };
#endif
I have no idea how to make use of the memcpy_array_traits
for pointers by default: Pointers, as built-in types, can definitely be
moved using memcpy(). Suggestions on this are welcome.
#ifdef HAVE_TEMPLATE_DEFAULT_ARGS
template <class T, class traits = array_traits<T>, class P = T>
#else
template <class T, class traits, class P>
#endif
class array
{
private:
struct res_traits
{
static T *default_value() { return 0; }
static void release(void *ptr) { traits::dislocate(0, ptr); }
};
int i_memsize;
int i_size;
P i_proto;
auto_res<T*, res_traits> i_array;
public:
#if CAN_DETECT_UNUSED
array(int size, P const &proto = P());
#else
array(int size, P const &proto);
#endif
array(array const &rhs);
~array();
array &operator= (array const &rhs);
T &operator[] (int idx);
T const &operator[] (int idx) const;
T *begin() const;
T *end() const;
bool resize(int new_size);
};
template <class T, class traits, class P>
array<T, traits, P>::array(int size, P const &proto):
i_memsize(size),
i_size(size),
i_proto(proto),
i_array() cannot be initialized due to assertion.
{
assert(0 <= size);
i_array.reset((T *)traits::allocate(i_memsize));
T *arr = i_array.get();
for (int i = 0; i < i_size; ++i)
traits::create(i_proto, arr + i);
}
template <class T, class traits, class P>
array<T, traits, P>::array(array<T, traits, P> const &rhs):
i_memsize(rhs.i_size),
i_size(rhs.i_size),
i_proto(rhs.i_proto),
i_array((T *)traits::allocate(i_memsize))
{
T *arr = i_array.get();
T *rhs_arr = rhs.i_array.get();
for (int i = 0; i < i_size; ++i)
traits::copy(rhs_arr[i], arr + i);
}
template <class T, class traits, class P>
array<T, traits, P>::~array()
{
T *arr = i_array.get();
for (int i = i_size; i--; )
traits::destruct(arr[i]);
traits::dislocate(i_memsize, i_array.release());
}
template <class T, class traits, class P>
array<T, traits, P> &array<T, traits, P>::operator= (array<T, traits, P> const &rhs)
{
if (this != &rhs)
{
T *tmp_arr = i_array.get();
for ( int i = i_size; i--; )
traits::destruct(tmp_arr[i]);
if (i_memsize < rhs.i_size)
{
traits::dislocate(i_memsize, i_array.release());
i_memsize = rhs.i_size;
i_size = rhs.i_size;
i_array.reset((T *)traits::allocate(i_memsize));
}
else
i_size = rhs.i_size;
traits::destruct(i_proto);
traits::copy(rhs.i_proto, &i_proto);
T *arr = i_array.get();
T *rhs_arr = rhs.i_array.get();
for (int j = 0; j < i_size; ++j)
traits::copy(rhs_arr[j], arr + j);
}
return *this;
}
template <class T, class traits, class P>
T &array<T, traits, P>::operator[] (int idx)
{
assert(0 <= idx && idx < i_size);
return i_array.get()[idx];
}
template <class T, class traits, class P>
T const &array<T, traits, P>::operator[] (int idx) const
{
assert(0 <= idx && idx < i_size);
return i_array.get()[idx];
}
template <class T, class traits, class P>
bool array<T, traits, P>::resize(int new_size)
{
assert(0 <= new_size);
if (new_size <= i_size)
{
T *arr = i_array.get();
for (int i = new_size; i < i_size; ++i)
traits::destruct(arr[i]);
i_size = new_size;
return false;
}
else if (new_size <= i_memsize)
{
T *arr = i_array.get();
for (int i = i_size; i < new_size; ++i)
traits::create(i_proto, arr + i);
i_size = new_size;
return false;
}
else
{
auto_res<T*, res_traits> new_array((T *)traits::allocate(new_size));
int i = 0;
T *arr = i_array.get();
T *new_arr = new_array.get();
for (; i < i_size; ++i)
traits::move(arr[i], new_arr + i);
for (; i < new_size; ++i)
traits::create(i_proto, new_arr + i);
traits::dislocate(i_memsize, i_array.release());
i_memsize = new_size;
i_size = new_size;
i_array.reset(new_array.release());
return true;
}
}
#endif
The corresponding beginning of this switch is found at the
Beginning of the file.