Article View: sci.virtual-worlds
Article #3006TECH: Test drivers sought for a C++ object
From: John Costella
Date: Sat, 23 Jan 1993 02:33
Date: Sat, 23 Jan 1993 02:33
248 lines
11319 bytes
11319 bytes
We have a C++ object that needs to be `test driven' on a variety of
platforms, and criticised wherever it either doesn't work or is
deemed unnecessarily inflexible.
The class essentially allows ``multi-dimensional arrays'' of arbitrary
objects to be dynamically allocated and manipulated with standard
[i][j][k][l] type of notation, where there are no restrictions on the
dynamical specification of any of the indices, and any special memory
handling routines (which can be completely arbitrary, and indeed can
be different for each index of the ``array'') are encapsulated into
classes derived from this base class.
If you are a C++ programmer, whether a CS undergrad or experienced
veteran, and would like to give the above class a test drive, please
send an e-mail to jpc@tauon.ph.unimelb.edu.au .
The class can be used `as is' for the supplied example derived classes
for bytes and bits, or classes can be derived for your own needs.
Further technical details for those interested are given below. The
class is freely usable for any purpose, and should run with any flavour
of C++. Preliminary documentation will be available.
John Costella
The University of Melbourne
----------------------------------------------------------------------------
John P. Costella School of Physics, The University of Melbourne
jpc@tauon.ph.unimelb.edu.au Tel: +61 3 543-7795, Fax: +61 3 347-4783
----------------------------------------------------------------------------
More details about the above class
==================================
(NOTE: Most of the following discussion applies to C without need
for any knowledge of C++.)
The class focusses on the somewhat primitive treatment that plain
C and C++ has for handling ``tensors'' of arbitrary objects.
By a ``tensor'' we mean what would be called in BASIC a ``multi-
dimensional array'' (we are borrowing the mathematical term
``tensor'' as a good fit, which has a descriptive language ready-
made for us).
The ``rank'' of a tensor of objects is essentially the number of
subscripts that are required to specify a single object. A rank 0
tensor is a ``scalar'', a rank 1 tensor is a ``vector'', a rank 2
tensor is a ``matrix'', a rank 3 tensor is like a 3-dimensional
rectangular prism of voxels, and so on.
A scalar of the object Object => Object scalar
A vector of objects Object => Object vector[ i ]
A matrix of objects Object => Object matrix[ i ][ j ]
A rank 3 tensor of objects Object => Object rank_3[ i ][ j ][ k ]
...and so on.
The problem is that vanilla C++ is really only geared up to handle
scalars and vectors of objects dynamically (and even the handling
of vectors keeps changing as C++ shakes down). You can write
int* scalar = new int;
*scalar = 7;
delete scalar;
int* vector = new int[ num ];
vector[ 3 ] = 12;
delete [] vector;
(for C, substitute `malloc' and `free' for the `new' and `delete'
operators; for older versions of C++, read:
delete [ num ] vector;
for the deletion of a vector), but you can't write
int* matrix = new int[ numx ][ numy ]; // WRONG
or
int** matrix = new int[ numx ][ numy ]; // ALSO WRONG
There are two standard ways to get around this problem. One is to
essentially lay all the ``rows'' of the matrix end-to-end and
allocate a single large vector:
int* matrixfudge = new int[ numx * numy ];
But then you need to perform a multiplication to access every
element:
i = matrixfudge[ y * numx + x ]; // Fudge for matrix[ y ][ x ]
and so not only is the syntactical cleanliness lost, you are also
invoking multiplications just to access an element. In general, for a
rank r tensor, you need to perform (r-1) obscure, processor-consuming
and error-prone multiplications. Consider, as an example, a rank 4
tensor:
temp1 = numw;
temp2 = numx * temp1;
temp3 = numy * temp2;
int* rankfour = new int[ numz * temp3 ];
// Fudge for i = rankfour[ z ][ y ][ x ][ w ] follows:
i = rankfour[ z * temp3 + y * temp2 + x * temp1 + w ];
This approach also requires that the whole memory for the tensor
be allocated in one huge contiguous chunk, which might not be
either possible or desirable.
The other standard workaround (* see footnote), which avoids the
obscurity, the performance penalty and the contiguity problems of
the above, at the small expense of a smaller number of extra pointers,
is to allocate a vector of pointers, each pointing to another vector
of pointers, each pointing to another vector of pointers, ..., and so
on until we finally have each of the last vector of pointers pointing
to a vector of objects. For example, a matrix of integers is
implemented via the code
int** matrix = new int*[ numy ];
for( int y = 0; y < numy; y++ )
matrix[ y ] = new int[ numx ];
Now we can validly use clean syntax:
i = matrix[ y ][ x ]; // Now OK
There are no multiplications needed to access an element, only
an additional pointer dereference per rank. And best of all, the
memory is allocated as (numy) separate rows, each of length (numx)
integers, rather than a single block of (numy*numx) integers.
The only size penalty is the vector of pointers, which is in
general insignificant compared to the matrix as a whole.
This second workaround is clearly the better of the two, but
it suffers from lack of code re-use. To implement a matrix of
doubles, or a matrix of Windows, or a matrix of any other arbitrary
object that we haven't even invented yet, we have to rewrite the
above code replacing `int' by `double' or `Win' or whatever. (* see
footnote for details) To then go to rank 3 objects we would have to
write another swag of routines to create a vector of matrices, and
again for rank 4, and so on. And then each of this large population
of routines would need to be modified if we needed a different
memory handling---say, __far or __huge pointers for 16-bit PC apps.
We'd end up with hundreds of different specialised routines just to
handle a handful of objects with a handful of ranks and a few memory
models.
Clearly, the desired solution is to create a generic Tensor class
that will automatically recurse down into itself to allocate and
free all the pointers needed for an arbitrary-rank tensor. But it
is slightly complicated because the Tensor class doesn't know what
sort of object it is that we want a tensor of (if we want to be
able to use it for any arbitrary objects), it doesn't know how
to allocate memory (if we want that to be changeable) and it
doesn't even know how large the pointers are that point to either
the objects or even its own subtensors (if we need to use
different types of pointers). In addition, virtual functions are
*disabled* within constructors (because the derived classes don't
exist at that point) so we can't use polymorphism to help us out.
Nevertheless, it is not impossible to write a Tensor class that
does this job. The tensors only recurse down to the vector level
(not the scalar level) to ensure that the tensor structure does not
take up more memory than the objects it houses. There is still a thin
layer of wrapping that needs to be performed at the vector level,
once for each memory model, and once for each different type of
object that needs to be tensorised. However, once this is done,
the job is complete. For example, if a BitTensor class wrapping is
written (a page or two of code), then one can create bitmaps via
the code
{
unsigned int rank = 2; // dynamically specify rank
unsigned long dimensions[ rank ] = { numx, numy }; // dynamically
// specify dims
BitTensor bitmap( rank, dimensions ); // create the tensor: actually
// dynamically allocated
bitmap[ 34 ][ 47 ] = 1; // access a bit entry in the bitmap
BitTensor bitmap2( bitmap ); // create a new tensor which is a
// copy of the first
}
// bitmaps are dynamically freed at end of scope
There is not even any need to delete the bitmaps when finished: the
objects `bitmap' and `bitmap2' are automatic variables (of size only
a few bytes) which contains pointers to its subtensors, and so on;
when the variables `bitmap' and `bitmap2' go out of scope, their
destructors automatically clean away the tensors.
Now imagine that you wanted to create simple 1-bit-deep voxmaps
to represent three-dimensional volumes (say, for spatial-occupancy
enumeration). But say that you need some number N of these voxmaps,
one for the volume surrounding each of the N participants in your
virtual world. Without writing a single extra line of code, you can
simply say
unsigned int rank = 4;
unsigned long dimensions[ rank ] = { N, sizex, sizey, sizez };
BitTensor vector_of_voxmaps( rank, dimensions );
// set the voxel ( 445, 28, 112 ) for the third participant.
vector_of_voxmaps[ 3 ][ 445 ][ 28 ][ 112 ] = 1;
// out-of-bounds automatically trapped and avoided for the following
vector_of_voxmaps[ N + 18 ][ 0 ][ 0 ][ 0 ] = 0;
By instead using a derived class which allows arbitrary floating
point values as indices to the closest voxel, a test for occupancy
could then be written
if( vector_of_voxmaps[ 3 ][ -4.77 ][ 12.3 ][ 196.65 ] )
; // do something meaningful
which is about as clean a syntax as one could ask for.
. . .
The tensor class described above is the one that requires test
drivers. It should be emphasised that the class is designed
for high performance and low overheads: using it for a tensor
structure of any type of object is at least as fast as any
other method, and the memory overheads are negligible.
If you would like a copy of class (supplied via source code in
plain ASCII), please follow the intructions at the start of this
post.
John Costella
---------------------------------------------------------------------
*Footnote: The `pointer to vector of pointers to vectors of pointers
to vectors of ...' trick is described in detail in
W. H. Press, B. P. Flannery, S. A. Teukolsky and W. T. Vetterling,
_Numerical_Recipes_In_C_ (Cambridge University Press, Cambridge,
1988), section 1.2, `Some C Conventions for Scientific Computing'.
The explosion in code needed to even service a handful of C built-in
types using this method, without the benefit of a Tensor class, is
exemplified by their library of utilities in Appendix D.
---------------------------------------------------------------------
Technical Details
Message-ID:
<1jvoupINNmdr@shelley.u.washington.edu>Path:
arc1-ca.pugleaf.net!sparky!uunet!zaphod.mps.ohio-state.edu!uwm.edu!ogicse!news.u.washington.edu!stein.u.washington.edu!hlabShow Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!sparky!uunet!zaphod.mps.ohio-state.edu!uwm.edu!ogicse!news.u.washington.edu!stein.u.washington.edu!hlab From: jpc@tauon.ph.unimelb.edu.au (John Costella) Newsgroups: sci.virtual-worlds Subject: TECH: Test drivers sought for a C++ object Message-ID: <1jvoupINNmdr@shelley.u.washington.edu> Date: 23 Jan 93 02:33:56 GMT Article-I.D.: shelley.1jvoupINNmdr Organization: University of Washington Lines: 248 Approved: cyberoid@milton.u.washington.edu NNTP-Posting-Host: stein.u.washington.edu Originator: hlab@stein.u.washington.edu