Possible Relatively Simple Way To Do Dynamic Multi-Dimensional Arrays In C Or C+

Started by Frederick J. Harris, February 01, 2014, 12:11:33 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

This post is in reply to a discussion James Fuller and I were having over in the PowerBASIC Cafe about C++, and particularly to my observation that multi-dimensional arrays in C++ were problematic.  Here is a link to the discussion there ...

http://www.powerbasic.com/support/pbforums/showthread.php?t=54305

Thanks for that link James.  I hadn't seen that one before but had found other similar ones involving template and vector solutions to this issue.  I did need a solution to this problem, and here is the one I came up with using your example ...


#include <cstdlib>
#include <cstdio>     // d1=1st dim; d2=2nd dim
#define  ar2(i,j)     pObj[(i)*(d2) + (j)]

int TestIt(int* pObj, int d2)
{
ar2(1,1)=77;
return ar2(0,1);
}

int main(void)
{
int* pObj=NULL;
int d1=3, d2=4;  // 3 rows; 4 cols

pObj=(int*)malloc(d1*d2*sizeof(int)); // allocate 12 int objects
if(pObj)
{
    ar2(0,0)=7;
    ar2(0,1)=123;
    printf("ar2(0,0) = %d\n",ar2(0,0));
    int RetVal=TestIt(pObj, d2);
    printf("ar2(1,1) = %d\n",ar2(1,1));
    printf("RetVal   = %d\n",RetVal);
    free(pObj);
}
getchar();

return 0;
}


Some notable differences in mine are that this implementation allows me to use PowerBASIC array-like syntax, i.e., ar2(1,1), rather than '[][]' type syntax.  The above compiles for me to 6K instead of 459K using vectors and templates. Of course, a PowerBASIC solution is simply this ...


#Compile Exe

Function TestIt(ar2() As Long) As Long
  ar2(1,1)=77
  Function=ar2(0,1)
End Function

Function PBMain() As Long
  Local d1,d2 As Long

  d1=3 : d2=4
  Dim Ar2(d1,d2) As Long
  ar2(0,0)=7
  ar2(0,1)=123
  Console.Print "ar2(0,0) = " ar2(0,0)
  Local RetVal As Long
  RetVal=TestIt(ar2())
  Console.Print "ar2(1,1) = " ar2(1,1)
  Console.Print "RetVal   = " RetVal
  Con.Waitkey$

  PBMain=0
End Function
 

That compiles to 13 K with PB CC6.   Of course, in PowerBASIC, some interesting things go on there.  Within function TestIt() the compiler is completely aware of everything regarding that array.  It would have generated an array descriptor for that array containing such information as the upper and lower bounds.  In my C++ macro version its all done by my macro chicanery.  Your template based example wouldn't suffer from that I don't suppose, because your TestIt() parameter carries information comparable to what PowerBASIC does.

I'm not completely happy with the implementation I've worked out, as I see some faults to it.  Specifically, I'm pretty hard set against using global variables in my programs, and in this entity ...


#define  ar2(i,j)     pObj[(i)*(d2) + (j)]


... pObj and d2 look suspiciously like global variables to me although they are just place holders in the macro.  I passed them into my TestIt() as parameters though;  they are needed there for the text substitution to work.  However, while I'm not completely happy with it as a solution, I'm happy enough with it to use it over a template based solution.

Of course, my trick is just to index correctly into a linear address space returned by an allocator so that it can be conceptualized as a two dimensional entity for algorithmic purposes where a solution can be conceptualized easier using row/col logic.  Here would be an example in two dimensions that outputs a row / col format of the underlying linear address space ...


#include <cstdlib>
#include <cstdio>     // d1=1st dim; d2=2nd dim
#define  ar2(i,j)     pObj[(i)*(d2) + (j)]

int main(void)
{
int* pObj=NULL;
int d1=3, d2=4, iCtr=0;  // 3 rows; 4 cols

pObj=(int*)malloc(d1*d2*sizeof(int)); // allocate 12 int objects
if(pObj)
{
    for(int i=0; i<d1; i++)            // if allocation successful
        for(int j=0; j<d2; j++)        // store nums 0 - 11 in
            ar2(i,j)=iCtr++;           // successive elements

    for(int i=0; i<d1; i++)
    {
        for(int j=0; j<d2; j++)
        {
            printf("ar2(%d,%d)=%d\t",i,j,ar2(i,j));  // output array
        }
        printf("\n");
    }
    free(pObj);
}
getchar();

return 0;
}


And output ...


ar2(0,0)=0      ar2(0,1)=1      ar2(0,2)=2      ar2(0,3)=3
ar2(1,0)=4      ar2(1,1)=5      ar2(1,2)=6      ar2(1,3)=7
ar2(2,0)=8      ar2(2,1)=9      ar2(2,2)=10     ar2(2,3)=11


Thinking long and hard about this ...

#define  ar2(i,j)     pObj[(i)*(d2) + (j)]

... suggests a way to extend it to three dimensions.  We've got the numbers 0 through 11 stored in a 3 X 4 row column tabular format.  Conceptualized in three dimensional space, consider that as the bottom floor of a square apartment complex, or the building blocks we played with as toddlers long ago.  So where will the next floor above start?  Well, obviously, at 12.  And of course we have a 3 X 4 tabular matrix and the product of those d1 (dimension 1) and d2 (dimension 2) numbers is 12.  That suggests this macro ...


#define ar3(i, j, k)  pObj[ (i)*(d2)  +  (j)  +  (k)*(d2)*(d1) ]


Examining that, if k = 1 and the 2nd dimension is 4 and the 1st dimension is 3, this coordinate in x,y,z 3d space ...

ar3(0, 0, 1)

... will resolve to the twelfth element from the array's base offset address.  And that's what we need.  Here is the two dimensional program above extended to 3 dimensional space (and in deference to C++ I used new instead of malloc and it cost me 13,000 bytes (6k with malloc - 19K with new)...


#include <cstdio>
#define ar3(i, j, k)  pObj[ (i)*(d2)  +  (j)  +  (k)*(d2)*(d1) ]

int main()
{
int d3=3,  d2=4, d1=3, iCtr=0;
int i,j,k;

int* pObj=(int*)new int[d1*d2*d3];
if(pObj)
{
    for(k=0; k<d3; k++)
        for(i=0; i<d1; i++)
            for(j=0; j<d2; j++)
                ar3(i,j,k)=iCtr++;

    for(k=0; k<d3; k++)
    {
        for(i=0; i<d1; i++)
        {
            for(j=0; j<d2; j++)
            {
                printf("%d\t",ar3(i,j,k));
            }
            printf("\n");
        }
        printf("\n");
    }

    delete [] pObj;
}
getchar();

return 0;
}

/*
0       1       2       3      // 0th layer, or in deference to mathematician's x,y,x space, z=k=0;
4       5       6       7
8       9       10      11

12      13      14      15     // layer/floor/block #1 = z=k=1
16      17      18      19
20      21      22      23

24      25      26      27     // top floor - z=k=2
28      29      30      31
32      33      34      35
*/


We're on a roll now, so why stop?  Anyone for four dimensions?  That's really the point of all this, because that's what I need to be able to translate my mission critical forestry data processing code over to C++ from PowerBASIC.   Here's the seemingly insurmountable obstacle ...


Type Ranges
  iMinSawDbh                         As Long
  iMaxSawDbh                         As Long
  iSawRange                          As Long
  iNumBlocks                         As Long
  iNumSawSuffixes                    As Byte
  iHighSawSuffix                     As Byte
  iNumPulpSuffixes                   As Byte
  iHighPulpSuffix                    As Byte
  iNumSawSpecies                     As Long
  iMinPulpDbh                        As Long
  iMaxPulpDbh                        As Long
  iPulpRange                         As Long
  iNumPulpSpecies                    As Long
End Type

Redim iBFVol(Rngs.iSawRange, Rngs.iNumSawSpecies, Rngs.iNumBlocks, Rngs.iNumSawSuffixes)
ReDim sngBFNums (Rngs.iSawRange, Rngs.iNumSawSpecies, Rngs.iNumBlocks, Rngs.iNumSawSuffixes)
Redim iCFVol(Rngs.iPulpRange, Rngs.iNumPulpSpecies, Rngs.iNumBlocks, Rngs.iNumPulpSuffixes)
Redim sngCFNums(Rngs.iPulpRange, Rngs.iNumPulpSpecies, Rngs.iNumBlocks, Rngs.iNumPulpSuffixes)
Redim iGross(Rngs.iNumBlocks, Rngs.iNumSawSpecies, Rngs.iNumSawSuffixes)
Redim cuxSawTotals(Rngs.iNumBlocks, Rngs.iNumSawSpecies, Rngs.iNumSawSuffixes)
Redim cuxPlpTotals(Rngs.iNumBlocks, Rngs.iNumPulpSpecies, Rngs.iNumPulpSuffixes)


Whoa!  How 'bout that?  You might wonder why anyone would need four dimensions.  I think I can make it easily clear.  In forestry timber data processing, timber buyers frequently want to see a tabular representation of the trees they wish to purchase where species are plotted across the top of the table and the diameter of the tree in inches is printed down the left side, and the table contains board foot volume estimates.  Here is an actual example ...


                                         Board Foot Volumes For All Trees In Overall Sale

Dbh       WP        PP        RM        RO        BO        SO        WO        CO        BB        YP       Hik       Asp    Totals
====================================================================================================================================
10       228       158         0         0         0         0         0         0         0         0         0         0       386
11       414       375         0         0         0         0         0         0         0         0         0         0       789
12       250       599      1121      2160      7735      5000      8889     44952      1726       508         0       330     73270
13       455       260       498      1616      6619      4351      7088     48121      2333       336       120       501     72298
14       313       924       797      3165      8410      7557      6415     48666      1620       387         0       571     78825
15       166         0       632      3193      9530      4820      5247     50089      1165      1240         0       329     76411
16         0       212       394      1877      5936      6018      5895     45883      1484       851         0       208     68758
17         0         0      1303      2733      4588      5782      3615     35448       700       728         0      1148     56045
18         0       308       827      1310      3912      4431      1389     25106       815         0         0       590     38688
19         0         0         0      1540      7121      3414      3103     14796       226       976         0         0     31176
20         0         0       285      1704      6538      1969       377      7714       253      1126         0       373     20339
21         0         0         0      1022      3296      1904      1972      5448         0      2544         0         0     16186
22         0         0         0      1339      3879      2393      2022      3130         0         0         0         0     12763
23         0         0         0       425      1318      2306         0      1499       348         0         0         0      5896
24         0         0         0         0      2028         0       562       473         0         0         0         0      3063
25         0         0         0         0      1692      1218      1295         0         0       779         0         0      4984
26         0         0         0       610       558         0         0       512         0      1008         0         0      2688
27         0         0         0         0       670         0         0         0         0         0         0         0       670
28         0         0         0         0         0         0         0         0         0         0         0         0         0
29         0         0         0       779         0         0         0         0         0         0         0         0       779
30         0         0         0       839         0         0         0         0         0         0         0         0       839
====================================================================================================================================
        1826      2836      5857     24312     73830     51163     47869    331837     10670     10483       120      4050    564853



     In the above table WP is White pine, PP Pitch pine, RM Red maple, RO Red oak, etc.  The economic reason such a table is valuable to timber buyers wishing to purchase timber is that the larger diameter trees are usually more valuable per board foot than the smaller diameter trees.  This is because when timber is sawn, higher grade lumber is produced from larger trees because knots and so forth on the lower parts of the bole are overgrown with more clear wood, and the yields of the higher grades of lumber is greater.  So a table such as above shows how the volumes are distributed across species by diameter.

     Of course, such a table produced by an input stream of random tree data as would be read in from our data collector files and tree tallies could be produced easily utilizing only two dimensional arrays.  So where would a need arise for even 3 dimensions?  Well, note the heading of that table, i.e., 'Board Foot Volumes For All Trees In Overall Sale'.  What about this ...







                                           Board Foot Volumes For All Trees In Block # 8

Dbh       WP        PP        RM        RO        BO        SO        WO        CO        BB        YP       Hik       Asp    Totals
====================================================================================================================================
10         0         0         0         0         0         0         0         0         0         0         0         0         0
11         0         0         0         0         0         0         0         0         0         0         0         0         0
12         0         0         0       486       539       720       174      6609         0         0         0         0      8528
13         0         0         0       360       906       415       189      9201         0         0         0         0     11071
14         0         0         0       357      1103       322         0      8299         0         0         0         0     10081
15         0         0         0         0       775         0         0     10425         0         0         0         0     11200
16         0         0         0       164       648       181         0      5952         0         0         0         0      6945
17         0         0         0       697       400       589         0      5069         0         0         0         0      6755
18         0         0         0         0       400         0         0      3213         0         0         0         0      3613
19         0         0         0         0       791         0         0      2126         0         0         0         0      2917
20         0         0         0         0       618         0         0       778         0         0         0         0      1396
21         0         0         0         0       729         0         0      1778         0         0         0         0      2507
22         0         0         0         0         0         0         0       259         0         0         0         0       259
23         0         0         0         0       425         0         0       678         0         0         0         0      1103
24         0         0         0         0       403         0         0         0         0         0         0         0       403
25         0         0         0         0         0         0         0         0         0         0         0         0         0
26         0         0         0         0         0         0         0         0         0         0         0         0         0
27         0         0         0         0         0         0         0         0         0         0         0         0         0
28         0         0         0         0         0         0         0         0         0         0         0         0         0
29         0         0         0         0         0         0         0         0         0         0         0         0         0
30         0         0         0         0         0         0         0         0         0         0         0         0         0
====================================================================================================================================
           0         0         0      2064      7737      2227       363     54387         0         0         0         0     66778




     Uh oh!  Now we're in trouble.  Our timber sales can have anywhere from 1 to 30 or 40 cutting blocks, and that's how the timber is actually paid for, and the cutting block boundaries are marked with paint.  So now we're into 3 dimensional arrays where the 'z' coordinate in Cartesian space is a cutting block.  And as you might be beginning to fear, that isn't the end.  Look at the title of that table, which reads 'Board Foot Volume For All Trees In Block #8'.  What does that little word 'All' mean?  Well, some of those trees in that table were further classified at the time of their marking by a forester as 'live', 'dead', 'low dbh sawtimber', or 'low value sawtimber'.  So that results in possibly four more tables for each block, and, four more for the totals of all blocks tables.  Here would be a subset of the 'Live' trees in Block #8 ...   


                                          Board Foot Volumes For Live Trees In Block # 8

Dbh       WP        PP        RM        RO        BO        SO        WO        CO        BB        YP       Hik       Asp    Totals
====================================================================================================================================
10         0         0         0         0         0         0         0         0         0         0         0         0         0
11         0         0         0         0         0         0         0         0         0         0         0         0         0
12         0         0         0       412       295       498        72      6312         0         0         0         0      7589
13         0         0         0       270       685       310       101      8931         0         0         0         0     10297
14         0         0         0       357       794       322         0      7980         0         0         0         0      9453
15         0         0         0         0       775         0         0     10076         0         0         0         0     10851
16         0         0         0         0       648       181         0      5804         0         0         0         0      6633
17         0         0         0       343       400       368         0      4953         0         0         0         0      6064
18         0         0         0         0       179         0         0      3213         0         0         0         0      3392
19         0         0         0         0       791         0         0      2126         0         0         0         0      2917
20         0         0         0         0       618         0         0       778         0         0         0         0      1396
21         0         0         0         0       729         0         0      1778         0         0         0         0      2507
22         0         0         0         0         0         0         0       259         0         0         0         0       259
23         0         0         0         0       425         0         0       678         0         0         0         0      1103
24         0         0         0         0       403         0         0         0         0         0         0         0       403
25         0         0         0         0         0         0         0         0         0         0         0         0         0
26         0         0         0         0         0         0         0         0         0         0         0         0         0
27         0         0         0         0         0         0         0         0         0         0         0         0         0
28         0         0         0         0         0         0         0         0         0         0         0         0         0
29         0         0         0         0         0         0         0         0         0         0         0         0         0
30         0         0         0         0         0         0         0         0         0         0         0         0         0
====================================================================================================================================
           0         0         0      1382      6742      1679       173     52888         0         0         0         0     62864 


Well, you might be thinking, "Couldn't the data be programmatically subsetted and multiple runs through the data be made so that only two or three dimensional arrays are needed?"  Yes, that would be possible, but where's the fun and glory in that, not to mention much sought after style points?

     So we need four dimensional arrays, and at least in PowerBASIC we're good up to 8 dimensions I believe.  But accomplishing four dimensions will yield me a style point count I can live with so we'll stop there.  But what if we need to do this in C++?  Well, we're on a roll, remember?   Where we last left off we had this for three dimensional arrays ...


#define ar3(i, j, k)  pObj[ (i)*(d2)  +  (j)  +  (k)*(d2)*(d1) ]


And using that we set the numbers 0 through 35 in our 3 X 4 X 3 (x, y, z) matrix.  Clearly if we are going to continue on into the not so easily visualized fourth dimension we are going to need an expression that fills the first 36 slots with 0 through 35, then start the fourth dimension with the number 36.  Borrowing from our three dimensional macro, this would be suggested ...


#define pObj(i,j,k,l)  pInt[(i)*(d2) + (j) + (k)*(d2)*(d1) + (l)*(d1)*(d2)*(d3)]


And here's a four dimensional array example of that ...


#include <cstdlib>
#include <cstdio>
#define ar4(i,j,k,l)  pObj[(i)*(d2) + (j) + (k)*(d2)*(d1) + (l)*(d1)*(d2)*(d3)]

int main()
{
int d4=3, d3=3,  d2=4, d1=2, iCtr=0;

int* pObj=(int*)malloc(d1*d2*d3*d4*sizeof(int));
int i,j,k,l;

if(pObj)
{
    for(l=0; l<d4; l++)
        for(k=0; k<d3; k++)
            for(i=0; i<d1; i++)
                for(j=0; j<d2; j++)
                    ar4(i,j,k,l)=iCtr++;

    for(l=0; l<d4; l++)
    {
        for(k=0; k<d3; k++)
        {
            for(i=0; i<d1; i++)
            {
                for(j=0; j<d2; j++)
                    printf("%d\t",ar4(i,j,k,l));
                printf("\n");
            }
            printf("\n");
        }
    }
    free(pObj);
}
getchar();

return 0;
}

/*
0       1       2       3
4       5       6       7

8       9       10      11
12      13      14      15

16      17      18      19
20      21      22      23

24      25      26      27
28      29      30      31

32      33      34      35
36      37      38      39

40      41      42      43
44      45      46      47

48      49      50      51
52      53      54      55

56      57      58      59
60      61      62      63

64      65      66      67
68      69      70      71
*/


     Thoughts anyone?  I've tested it pretty well, and everything seems to be working as well as I can discern.  In terms of your vector and template implementations James, wouldn't an extension of your two dimensional template based example to three or four dimensions get pretty messy?  That syntax looks pretty forbidding to me.  And I'm inclined to believe that unclear or messy syntax can become a problem in coding because it can get in the way or impair our ability to reason complex problems through.  I've posted about this previously, but I strongly suspect that the language and symbols we use to express a problem affects our ability to reason correctly and further affects the conclusions we arrive at.  There is a social science statement about this termed the Sapier-Whorf Hypothesis ...

http://en.wikipedia.org/wiki/Linguistic_relativity
  •  

James C. Fuller

Fred,
  I do not like globals and will do quite a lot to avoid them.
Also I want to learn C++ better so your "c" approach leaves me less than thrilled :)
I am not too concerened about size although I have switched To VS 2013 as my main compiler because of the HUGE size of exe's using any of the MinGW derivitives. I know why (static linking) but as long as VS 2013 works I'm a fan.
  I found this on a forum somewhere? Google is your friend.

James


#include <iostream>
#include <vector>
using namespace std;
typedef char *PCHAR;
class Matrix4
{
private:
    std::vector<int> storage;
    int bb;
    int cc;
    int dd;
public:
    Matrix4( int a, int b, int c, int d ):
    storage( a*b*c*d ), bb( b ), cc( c ), dd( d ) { }
    int& operator()( int a, int b, int c, int d ) {
        return storage.at( bb * cc * dd * a + cc * dd * b + dd * c + d );
    }
};


int main ()
{
    int      d4 = 3;
    int      d3 = 3;
    int      d2 = 4;
    int      d1 = 2;
    int      iCtr = 0;
    int      i;
    int      j;
    int      k;
    int      l;

    Matrix4  ar4(d1, d2, d3, d4);

    for(l = 0; l < d4; l++)
        for(k = 0; k < d3; k++)
            for(i = 0; i < d1; i++)
                for(j = 0; j < d2; j++)
                    ar4(i, j, k, l) = iCtr++;


    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                    printf("%d\t", ar4(i, j, k, l));
                printf("\n");
            }
            printf("\n");
        }
    }

    system("pause");
}




  •  

James C. Fuller

Fred,
  Here is another method using the boost library. Most of it is way above my abilities but I have it installed to help with my learning process. Many examples use boost and it's a good idea to have at least a passing knowledge of it.

James



#include "boost/multi_array.hpp"
typedef boost::multi_array<int, 4> array_type;
typedef array_type::index index;


int main ()
{
    int      d4 = 3;
    int      d3 = 3;
    int      d2 = 4;
    int      d1 = 2;
    int      iCtr = 0;
    index    i;
    index    j;
    index    k;
    index    l;
    array_type  ar4(boost::extents[d1][d2][d3][d4]);
    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                {
                    ar4[i][j][k][l] = iCtr++;
                }

            }

        }

    }

    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                    printf("%d\t", ar4[i][j][k][l]);
                printf("\n");
            }
            printf("\n");
        }
    }
    system("pause");
}

  •  

Frederick J. Harris

Well Jim, now you've gone and shamed me into looking at a C++ implementation of the thing! :)

And I admit, I hadn't ever done that.  The #define macro is something I had come up with years ago and I simply 'dusted it off' for this 2nd look at the issue.  But in the back of my mind all the time I knew a class based implementation of the thing would involve overloading either the [], or () operators.  Those particular operators I had never overloaded, but I knew at least one or both of them could be overloaded.  Never having done that, I was kind of drawing a mental blank on it.  For some reason overloading the '=' or '+' operators like in my string class never bothered me, but [], () did.  Well, just took a crack at it, and it seemed to work out pretty easy.  Take a look at this if you are interested.  I started out with just the easy 2 Dim case, but I imagine extending it to 3 or 4 or more dims would follow natural like ...


#include <cstdio>
#include <cstdlib>

class CArray
{
public:
CArray(int r,int c)
{
  int iNumElements=r*c;
  this->pInts=new int[iNumElements];
  row=r, col=c;
  for(int i=0; i<iNumElements; i++)
      pInts[i]=i;
}

int operator()(int iRow, int iCol)
{
  return pInts[iRow*col+iCol];
}

~CArray()
{
  delete [] this->pInts;
}

private:
int* pInts;
int row;
int col;
};

int main(void)
{
CArray ar2(3,4);

for(int i=0; i<3; i++)
{
     for(int j=0; j<4; j++)
     {
         printf("%d\t",ar2(i,j));
     }
     printf("\n");
}
getchar();

return 0;
}

/*
0       1       2       3
4       5       6       7
8       9       10      11
*/


You know though, in reflecting on it, that original #define thing of mine really doesn't suffer from the global thing like I mentioned.  At least I don't think it does, does it?  I mean, for something to be global, storage needs to be allocated, doesn't it?  They are simply at file scope, but then so would my CArray class above.  But instances of it would be at procedure scope, unless one purposly created an actual instance/variable at global or file scope.

The whole thing does lend itself to a template implementation, so that instances of any variable type could be dealt with.  As things stand now, I know I'm going to look into this further.  One thing I can state for absolute certainty is that no application of mine will ever, under any circumstances, have a #include <iostream> in it.  I'm aware of the boost libraries - I imagine a lot of C++ coders use them, and I'm sure there is well thought out useful code there. 

In terms of the size thing, I'm not that unreasonable.  Its like with money.  If I want to buy a widget, and the going price for widgets is $20, and widgets are a good and useful thing to have, then I'm willing to pay maybe $20 to $25 for a widget.  But if somebody tries to sell me a widget for $459 I'm gonna be a bit leery and ask just what I'm getting for the extra $430.  I can get a "Hello, World!" output to my console screen for 6K with printf.  With iostream and std::cout its like 450K, and it takes a couple extra characters to type in to boot.  That's the point at which I take offense.

I do use classes in my C++ code.  Not a lot, but I do use them.  But its always based on a benifit / cost type analysis.  If the benifits are big and the costs small - then I go with them.  But usually its the other way around, so I pass. 

I want to thank you for taking as much time with this as you have.  As you can see, it is a significant issue I'm now dealing with.
  •  

Frederick J. Harris

#4
The last version was kind of funky - the numbers were assigned in the constructor.  In other words, the class had no mechanism to assign numbers to the array - only retrieve them.  But modifying the CArray::operator() function to return a reference to an int allows the function call to appear on the left side of an assignment statement because its an LValue...

Ar(2,2) = whatever;

Here is the next version ...


#include <cstdio>
#include <memory.h>

class CArray2
{
public:
CArray2(int r,int c)
{
  int iNumElements=r*c;
  this->pInts=new int[iNumElements];
  memset(this->pInts,0,iNumElements);
  row=r, col=c;
}

int& operator()(int iRow, int iCol) { return pInts[iRow*col+iCol]; }
~CArray2() { delete [] this->pInts; }

private:
int* pInts;
int row;
int col;
};


int main(void)
{
CArray2 ar2(3,4);
int iCtr=0;
int i,j;

for(i=0; i<3; i++)
     for(j=0; j<4; j++)
         ar2(i,j)=iCtr++;

for(int i=0; i<3; i++)
{
     for(int j=0; j<4; j++)
     {
         printf("%d\t",ar2(i,j));
     }
     printf("\n");
}
getchar();

return 0;
}

/*
0       1       2       3
4       5       6       7
8       9       10      11

*/


And remember what I said above about my benifit / cost analysis thing, that is, look at the benifits of encapsulating some functionality in a class, then comparing that against the code bloat?  Well, on this thing the bloat is zero! 

What I did is add my string class by itself to the project and remark out all the array class to see the size just with my string class alone.  The size on disk was 28672 bytes.  Of course, by doing that the compiler pulled in all the code needed to handle new and delete [], so on and so forth.  Then I uncommented out the CArray class and the little bit of code in main, and guess what??? Still 28672!  How do you like them apples?  I'm 'goin with it! :)
  •  

Frederick J. Harris

Works good as template.  Here is for ints ...


#include <cstdio>
#include <memory.h>

template <class datatype> class CArray2
{
public:
CArray2(int r,int c)
{
  int iNumElements=r*c;
  this->pObjs=new datatype[iNumElements];
  memset(this->pObjs,0,iNumElements*sizeof(datatype));
  row=r, col=c;
}

datatype& operator()(int iRow, int iCol) { return pObjs[iRow*col+iCol]; }
~CArray2() { delete [] this->pObjs; }

private:
datatype* pObjs;
int row;
int col;
};

int main(void)
{
CArray2<int> ar2(3,4);
int iCtr=0;
int i,j;

for(i=0; i<3; i++)
     for(j=0; j<4; j++)
         ar2(i,j)=iCtr++;

for(i=0; i<3; i++)
{
     for(j=0; j<4; j++)
         printf("%d\t",ar2(i,j));
     printf("\n");
}
getchar();

return 0;
}


And here is with strings.  Now it uses my string class.  You could easily convert it over to the std lib's string class too.  However, if you want to use mine I've got it here in a few places - best one would be my code posting for the x64/x86 C++ grid.  You need to get rid of the UNICODE defines and make sure that ONLY_NEED_PARSE thing or whatever it was is set up right.  Anyway ...


#include <windows.h>
#include <cstdio>
#include <tchar.h>
#include <memory.h>
#include "Strings.h"

template <class datatype> class CArray2
{
public:
CArray2(int r,int c)
{
  int iNumElements=r*c;
  this->pObjs=new datatype[iNumElements];
  memset(this->pObjs,0,iNumElements*sizeof(datatype));
  row=r, col=c;
}

datatype& operator()(int iRow, int iCol) { return pObjs[iRow*col+iCol]; }
~CArray2() { delete [] this->pObjs; }

private:
datatype* pObjs;
int row;
int col;
};

int main(void)
{
CArray2<String> ar2(3,4);
String s,s1,s2;
int i,j;

for(i=0; i<3; i++)
{
     for(j=0; j<4; j++)
     {
         s="(", s1=i, s2=j;
         s=s+s1+","+s2+")";
         ar2(i,j)=s;
     }
}
for(i=0; i<3; i++)
{
     for(j=0; j<4; j++)
         printf("%s\t",ar2(i,j).lpStr());
     printf("\n");
}
getchar();

return 0;
}

/*
(0,0)   (0,1)   (0,2)   (0,3)
(1,0)   (1,1)   (1,2)   (1,3)
(2,0)   (2,1)   (2,2)   (2,3)
*/
  •  

Frederick J. Harris

OK, here's where I'm at now.  One of my main requirements regarding dynamic multi-dimensional arrays is that they be straight forward to pass in parameter lists.  PowerBASIC has an extremely clean syntax for this which is also tremendously efficient, in that when passing a dynamic array, all one needs to do is specify the array name followed with an empty set of parentheses, i.e.,


Dim x,y As Long
x=10
y=20
Dim iArr(x,y) As Long
...
...
Foo(iArr())
...

Sub Foo(iArr() As Long)
...
...
End Function


Of course, no matter how much data the array contains, passing it is highly efficient because all that is passed is an address - probably through an array descriptor or something proprietary to the compiler.

In complex applications such as some of those I work on, some of the procedures are rather complex and involved with a lot of various types of arrays being passed through parameter lists - sometimes fairly basic types of parameters such as arrays of ints or floats, but other vtimes arrays of other more complex objects.  Here is an example ...


Function blnExamineDatabases _
  ( _
    Byref fmt()                                                 As Fmt19, _
    Byref td                                                    As TallyData, _
    Byval pFiles                                                As szFilePaths Ptr, _
    Byref Rngs                                                  As Ranges, _
    Byref iLine                                                 As Long, _
    Byval frmProcessSale                                        As Dword, _
    Byref iSawSp()                                              As Long, _
    Byref iPlpSp()                                              As Long, _
    Byref iBlocks()                                             As Long, _
    Byref iSawSuffixes()                                        As Long, _
    Byref iPlpSuffixes()                                        As Long _
  ) As Long
  Local iMdbSawSp(),iMdbPlpSp(),iCdbSawSp(),iCdbPlpSp()         As Long
  Local iMdbSawBks(),iMdbPlpBks(),iCdbBks()                     As Long
  Local iMdbSawSfx(),iMdbPlpSfx(),iCdbSawSfx(),iCdbPlpSfx()     As Long
  Local MrkRngs,CruRngs                                         As Ranges
  Local dbMrk,dbCru                                             As ISql
  Local pZStr                                                   As ZStr Ptr
  Local ptrLines                                                As ZStr Ptr
  Local szBuffer                                                As ZStr*192
  Local szQuery                                                 As ZStr*512
  Local iCtr,blnFound                                           As Long
  Local Sql                                                     As ISql
  Register i                                                    As Long
  Register j                                                    As Long

  #If %Def(%Debug)
      Print #fp, "  Entering blnExamineDatabases()"
      Print #fp, "    @pFiles.MdbFile    = " @pFiles.MdbFile
      Print #fp, "    @pFiles.CdbFile    = " @pFiles.CdbFile
      Print #fp, "    td.blnMdbData      = " td.blnMdbData
      Print #fp, "    td.blnPrismCruises = " td.blnPrismCruises
  #EndIf
  ....
  ....


So the point I'm trying to make is that I need a syntactically palatable syntax for these sorts of things.  I'm afraid things fell through for me when I used my last template example and attempted to pass a templated object as a parameter.  Let me 1st post a slightly modified version of my earlier example with ints, but modified so as to do the display through a function Dump(CArray2& ar) ...


#include <cstdio>
#include <memory.h>

class CArray2
{
public:
CArray2(int r,int c)
{
  int iNumElements=r*c;
  this->pInts=new int[iNumElements];
  memset(this->pInts,0,iNumElements);
  row=r, col=c;
}

int& operator()(int iRow, int iCol) { return pInts[iRow*col+iCol]; }
~CArray2() { delete [] this->pInts; }

private:
int* pInts;
int row;
int col;
};


void Dump(CArray2& ar)
{
for(int i=0; i<3; i++)
{
     for(int j=0; j<4; j++)
     {
         printf("%d\t",ar(i,j));
     }
     printf("\n");
}
}


int main(void)
{
CArray2 ar2(3,4);
int iCtr=0;
int i,j;

for(i=0; i<3; i++)
     for(j=0; j<4; j++)
         ar2(i,j)=iCtr++;
Dump(ar2);
getchar();

return 0;
}


The above works very well, and I'd consider its syntax as elegant and straight forward as PowerBASIC's.  Dump is simply specified as a reference to a CArray2 object.  Now, here's all I'm personally able to come up with if templates are used.  I actually spent a lot of time studying these things years ago, and I actually had to really dig to find the code which showed me how to do this.  I finally found it on an old Win 2000 laptop of mine, and upon reviewing the code I remembered the hard time I originally had trying to figure this out.  I recall now I had to ask a www.daniweb.com in their C++ board to get somebody to show me how to do this.  Here goes ...


#include <cstdio>
#include <memory.h>


template <class datatype> class CArray2
{
public:
CArray2(int r,int c)
{
  int iNumElements=r*c;
  this->pObjs=new datatype[iNumElements];
  memset(this->pObjs,0,iNumElements*sizeof(datatype));
  row=r, col=c;
}

datatype& operator()(int iRow, int iCol) { return pObjs[iRow*col+iCol]; }
~CArray2() { delete [] this->pObjs; }

private:
datatype* pObjs;
int row;
int col;
};


template<typename type> void Dump(CArray2<type>& ar)
{
for(int i=0; i<3; i++)
{
     for(int j=0; j<4; j++)
     {
         printf("%d\t",ar(i,j));
     }
     printf("\n");
}
}


int main(void)
{
CArray2<int> ar2(3,4);
int iCtr=0;
int i,j;

for(i=0; i<3; i++)
     for(j=0; j<4; j++)
         ar2(i,j)=iCtr++;
Dump(ar2);
getchar();

return 0;
}


So how do you like this for a function declaration ...

template<typename type> void Dump(CArray2<type>& ar)

compared to this ...

void Dump(CArray2& ar)

I will admit I got it to work by fooling around with my old code, but I'm not going to claim I understand it.  Perhaps that book with the red cover and the winding road explains that James.  For me though its too murky.  I think I'm going to just use standard classes instead of templated classes for my array work.  By the way, I had looked at that book at Amazon.com.  Was thinking of getting it at one time.  That road is probably in Finland or Sweeden or Norway, but it reminds me of the Cinnamon Pass jeep trail I drove over the Rockies last year.
  •  

James C. Fuller

Fred,
  I like the Class implementation.
You can create whatever type you want by changing the vector type.
Pass it by reference to a function.
You are also using familiar ar(x,y) notation to access
James



#include <vector>

class Matrix4
{
private:
    std::vector<int>  storage;
    int      bb;
    int      cc;
    int      dd;

public:
    Matrix4 (int a, int b, int c, int d): storage(a*b*c*d), bb(b), cc(c), dd(d)
    {
    }
    int& operator () (int a, int b, int c, int d)
    {
        return storage.at(bb * cc * dd * a + cc * dd * b + dd * c + d);
    }

};


class Matrix2
{
private:
    std::vector<long>  storage;
    long      bb;

public:
    Matrix2 (long a, long b): storage(a*b), bb(b)
    {
    }
    long& operator () (long a, long b)
    {
        return storage.at(bb * a + b);
    }

};


int TestIt (Matrix4&  ar4)
{
    int      d4 = 3;
    int      d3 = 3;
    int      d2 = 4;
    int      d1 = 2;
    int      iCtr = 0;
    int      i;
    int      j;
    int      k;
    int      l;
    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                    printf("%d\t", ar4(i, j, k, l));
                printf("\n");
            }
            printf("\n");
        }
    }
    return ar4(1, 1, 1, 1);
}


int TestIt2 (Matrix2&  ar2)
{
    int      d2 = 4;
    int      d1 = 2;
    int      iCtr = 0;
    int      i;
    int      j;
    for(i = 0; i < d1; i++)
    {
        for(j = 0; j < d2; j++)
            printf("%d\t", ar2(i, j));
        printf("\n");
    }
    printf("\n");
    return ar2(1, 1);
}


int main ()
{
    int      d4 = 3;
    int      d3 = 3;
    int      d2 = 4;
    int      d1 = 2;
    int      iCtr = 0;
    int      i;
    int      j;
    int      k;
    int      l;
    int      RetVal;
    Matrix4  ar4(d1, d2, d3, d4);
    Matrix2  ar2(d1, d2);
    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                {
                    ar4(i, j, k, l)        = iCtr++;
                }
            }
        }
    }


    for(i = 0; i < d1; i++)
    {
        for(j = 0; j < d2; j++)
        {
            ar2(i, j)    = iCtr++;
        }
    }

    RetVal = TestIt( ar4);
    printf("% d\n", (int)RetVal);

    RetVal = TestIt2( ar2);
    printf("% d\n", (int)RetVal);
   
    system("pause");
}


  •  

Frederick J. Harris

Hi Jim!

     That looks pretty nice.  And its only 44K with my older MinGW Code::Blocks version.  I've only barely used vectors, so I'm unfamiliar with that 'storage' symbol.  In any case, here's the almost imperceptively altered version that compiled for me without any warnings or errors...


#include <vector>
#include <cstdio>
#include <cstdlib>

class Matrix4
{
private:
    std::vector<int>  storage;
    int      bb;
    int      cc;
    int      dd;

public:
    Matrix4 (int a, int b, int c, int d): storage(a*b*c*d), bb(b), cc(c), dd(d)
    {
    }
    int& operator () (int a, int b, int c, int d)
    {
        return storage.at(bb * cc * dd * a + cc * dd * b + dd * c + d);
    }

};


class Matrix2
{
private:
    std::vector<long>  storage;
    long      bb;

public:
    Matrix2 (long a, long b): storage(a*b), bb(b)
    {
    }
    long& operator () (long a, long b)
    {
        return storage.at(bb * a + b);
    }

};


int TestIt (Matrix4&  ar4)
{
    int      d4 = 3;
    int      d3 = 3;
    int      d2 = 4;
    int      d1 = 2;
    int      i;
    int      j;
    int      k;
    int      l;
    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                    printf("%d\t", ar4(i, j, k, l));
                printf("\n");
            }
            printf("\n");
        }
    }
    return ar4(1, 1, 1, 1);
}


int TestIt2 (Matrix2&  ar2)
{
    int      d2 = 4;
    int      d1 = 2;
    int      i;
    int      j;
    for(i = 0; i < d1; i++)
    {
        for(j = 0; j < d2; j++)
            printf("%d\t", (int)ar2(i, j));
        printf("\n");
    }
    printf("\n");
    return ar2(1, 1);
}


int main ()
{
    int      d4 = 3;
    int      d3 = 3;
    int      d2 = 4;
    int      d1 = 2;
    int      iCtr = 0;
    int      i;
    int      j;
    int      k;
    int      l;
    int      RetVal;
    Matrix4  ar4(d1, d2, d3, d4);
    Matrix2  ar2(d1, d2);
    for(l = 0; l < d4; l++)
    {
        for(k = 0; k < d3; k++)
        {
            for(i = 0; i < d1; i++)
            {
                for(j = 0; j < d2; j++)
                {
                    ar4(i, j, k, l)        = iCtr++;
                }
            }
        }
    }


    for(i = 0; i < d1; i++)
    {
        for(j = 0; j < d2; j++)
        {
            ar2(i, j)    = iCtr++;
        }
    }

    RetVal = TestIt( ar4);
    printf("% d\n", (int)RetVal);

    RetVal = TestIt2( ar2);
    printf("% d\n", (int)RetVal);

    system("pause");
}


     I'm going back on something I said earlier.  I've decided to use templates afterall, in spite of the fact my first go around with them many years ago left a really unfavorable impression on me.  What changed my mind is the forestry data processing code I'm working on as I figure this C++ array stuff out requires multi-dimensional arrays of the same rank in many cases - but differing in data type, i.e., a four dim. array of both floats and ints, for example.  Now that exact sort of thing is exactly the text book case of how C++ authors usually present templates as well as their justification for them.  So, while the actual function signatures look formitably ugly when templates are passed as parameters, their actual use in the functions is exactly the same as non - templated arrays.  That's my excuse for changing my mind anyway!

     I see in your example above your classes (Matrix4 and Matrix2) are non-templated classes, i.e., standard classes, but of course you have vector objects within them to encapsulate the array.  And vectors are created with templates and use the template < ... > syntax to specify the actual variable type.  While my memory on vectors is poor, I do seem to recall various PowerBASIC array type interfaces are available to them such as methods to return their upper and lower bounds, etc.  More about that later. 

Anyway, if you recall that example I posted the other day showing how to pass a templated variable as a parameter ... that example showed how to do it with only one parameter.  Here's what I got to work in my actual work problem where I had three templated parameters ...


template<typename t1, typename t2, typename t3> bool blnDoCulls(HANDLE hFile, int iNRecs, int* pSp, int* pSfxs, CArray2<t1>& Cull, CArray2<t2>& CullCount, CArray2<t3>& Defect)
{
int h,i,j,iDefCols,iBytes,iLen,iSpecies,iSuffix;
String strRealSp,strSuffix,strSpecies;
Tree t;

iDefCols=pSfxs[0]+1;
for(i=1; i<=pSp[0]; i++)
{
     Cull(i-1,0)=pSp[i];
     CullCount(i-1,0)=pSp[i];
}
if(SetFilePointer(hFile,0,NULL,FILE_BEGIN)!=0xFFFFFFFF)
{
    iBytes=sizeof(t);
    for(h=1; h<=iNRecs; h++)
    {
        Get(hFile,h,&t,iBytes);
        if(t.BkCtr%10==0)
        {
           strSpecies=t.Sp;
           iLen=strSpecies.Len();
           if(iLen<2 || iLen>3)
              return false;
           if(iLen==3)
              strRealSp=strSpecies.Left(2);
           else
              strRealSp=strSpecies.Left(1);
           strSuffix=strSpecies.Right(1);
           iSpecies=strRealSp.iVal();
           iSuffix=strSuffix.iVal();
           for(i=0; i<pSp[0]; i++)
           {
               if(iSpecies==pSp[i])
                  break;
           }
           for(j=1; j<=pSfxs[0]; j++)
           {
               if(iSuffix==pSfxs[j])
                  break;
           }
           Cull(i-1,j)=Cull(i-1,j)+t.Cull;
           CullCount(i-1,j)=CullCount(i-1,j)+1;
        }
    }
    for(i=0; i<pSp[0]; i++)
    {
        for(j=1; j<=pSfxs[0]; j++)
        {
            if(CullCount(i,j))
            {
               Defect(i,j) = (100 - Cull(i,j)/CullCount(i,j));
               Defect(i,j) = Defect(i,j)/100;
            }
            else
            {
               Defect(i,j)=1.0;
            }
        }
    }
}

return true;
}
 

     The reason I posted that is that it wasn't immediately obvious to me how to do it when extending that earlier example to three templated parameters.  Now its clearer to me.  One must have seperate names for the type of each templated variable, which in the case above would be t1, t2, and t3.  What it looks like the designers of this did was attempt to carry through the ability to substitute variable types even to functions that operate on templated variables.  Looking at it in hind sight that makes sense, but it wasn't obvious to me at first. 

     In the code above the 1st two templated arrays are ints, and the last is a double.  This is completely unrelated, and I'm sure the code above doesn't interest you as I just wanted to show the function signature, but if you look at the code you'll see Get() and Put() there for reading a file, which are wrappers I made for ReadFile() and WriteFile().  If you need anything like that, here are my versions ...


DWORD Put(HANDLE hFile, UINT iRecNum, LPCVOID lpRec, DWORD dwSize)   //this is a wrapper around WriteFile()
{                                                                    //and gives me a BASIC language like
DWORD dwWritten;                                                    //syntax for random access file writting.
UINT iRet=FALSE;                                                    //ditto for Get() below.

if(iRecNum)
{
    if(SetFilePointer(hFile,dwSize*(iRecNum-1),NULL,FILE_BEGIN)!=0xFFFFFFFF)
    {
       iRet=WriteFile(hFile,lpRec,dwSize,&dwWritten,NULL);
       if(iRet==FALSE||dwWritten!=dwSize)
          return FALSE;
       else
          return TRUE;
    }
    else
       return FALSE;
}

return FALSE;
}


DWORD Get(HANDLE hFile, UINT iRecNum, LPVOID lpRec, DWORD dwSize)
{
UINT iRet=FALSE;
DWORD dwRead;

if(iRecNum)
{
    if(SetFilePointer(hFile,dwSize*(iRecNum-1),NULL,FILE_BEGIN)!=0xFFFFFFFF)
    {
       iRet=ReadFile(hFile,lpRec,dwSize,&dwRead,NULL);
       if(iRet==FALSE||dwRead!=dwSize)
          return FALSE;
       else
          return TRUE;
    }
    else
       return FALSE;
}

return FALSE;
}


     Getting back to arrays, what I've done is include a blnMemoryIsGood() member, and a UBound() member.  I like that.  The UBound thing allows one to run the for loop to ar.UBound(1) or ar.UBound(2), for example, with a 2d array.  The blnMemoryIsGood() thing tests that private member pObj isn't zero as it would be if the class' memory allocation failed.  I think there is a C++ functionality - AttachNewErrorHandler() or something like that for failed Constructor calls, but I'm not persuing it at this time.  Here is a cool example that's just 2d, but uses the templated class to make the int array we've been playing with, and a string array.  The example uses the std::string, so you don't need my string class for this.  For me, its coming out 46K with the std::string, and 28K with my string class.  The output looks like this ...


0       1       2       3
4       5       6       7
8       9       10      11

(0,0)   (0,1)   (0,2)   (0,3)
(1,0)   (1,1)   (1,2)   (1,3)
(2,0)   (2,1)   (2,2)   (2,3)



#include <cstdio>     // 45K
#include <string>
#include <memory.h>


template <class datatype> class CArray2
{
public:
CArray2(int r,int c) : pObjs(0)
{
  int iNumElements=r*c;
  this->pObjs=new datatype[iNumElements];
  row=r, col=c;
}

datatype& operator()(int iRow, int iCol)
{
  return pObjs[iRow*col+iCol];
}

bool blnMemoryIsGood()
{
  return !!pObjs;
}

int UBound(int iDim)
{
  if(iDim==1)
     return row;
  if(iDim==2)
     return col;
  else
     return 0;
}

~CArray2()
{
  delete [] this->pObjs;
}

private:
datatype* pObjs;
int row;
int col;
};


template<typename type1, typename type2> void Output(CArray2<type1>& strArray, CArray2<type2>& iArray)
{
for(int i=0; i<iArray.UBound(1); i++)
{
     for(int j=0; j<iArray.UBound(2); j++)
         printf("%d\t",iArray(i,j));
     printf("\n");
}
printf("\n");
for(int i=0; i<strArray.UBound(1); i++)
{
     for(int j=0; j<strArray.UBound(2); j++)
         printf("%s\t",strArray(i,j).c_str());
     printf("\n");
}
}


int main()
{
CArray2<std::string> strAr2(3,4);
CArray2<int> iAr2(3,4);
char szBuffer[16];
int iCtr=0;

if(strAr2.blnMemoryIsGood() && iAr2.blnMemoryIsGood())
{
    for(int i=0; i<iAr2.UBound(1); i++)
        for(int j=0; j<iAr2.UBound(2); j++)
            iAr2(i,j)=iCtr++;
    for(int i=0; i<strAr2.UBound(1); i++)
    {
        for(int j=0; j<strAr2.UBound(2); j++)
        {
            strAr2(i,j)="(";
            sprintf(szBuffer,"%d",i);
            strAr2(i,j)=strAr2(i,j)+szBuffer+",";
            sprintf(szBuffer,"%d",j);
            strAr2(i,j)=strAr2(i,j)+szBuffer+")";
        }
    }
    Output(strAr2,iAr2);
}
getchar();

return 0;
}


     By the way, using the std::string class how do you convert a string representation of a number to a numeric value?  I wasn't sure how to do that so I used sprintf above.  But there must be a C++ version?
With my string class, I used to have originally a *.CStr() member, but I got rid of that a long time ago by just using overloaded assignment statements, whereby if a string on the right is assigned to a numeric variable on the left, it simply converts it.  Here is what the above std::string conversions look like in my string class if s is a String variable...


strAr2(i,j)="(";
s=i;                                      <<<<<
strAr2(i,j)=strAr2(i,j)+s+",";
s=j;                                       <<<<<
strAr2(i,j)=strAr2(i,j)+s+")";

  •  

James C. Fuller

Fred,
  You forget I do not DO c++. :) Everything I post is cleaned up bc9Basic translations.

I use C++11 exclusivley so this is what I use. See Numeric conversions near the bottom.
http://en.cppreference.com/w/cpp/string/basic_string


I try to use c++ streams for most of my File IO just for practice:
I have std::string wrappers for most  basic string syntax functions (left,mid,instr....)
This is part of my DoCppFixUps from bc9:

int DoCppFixUps (char* f)
{
  fstream  f1;
  fstream  f2;
  stdstr   sLine;
  stdstr   sLast;
  stdstr   sLeft1;
  stdstr   sLeft2;
  char    sftmp[cSizeOfDefaultString]={0};
  static int      NeedConst;
  int      IoErr={0};
  strcpy(sftmp, join(2,f,"._tmp"));
  f1.open(f,ios::in);
  if(f1.is_open())
    {
      f2.open(sftmp,ios::out);
      if(f2.is_open())
        {
          while(f1.good())
            {
              getline(f1,sLine);

              if(instr(sLine," ->"))
                {
                  sLine= replace( sLine," ->","->");
                }

              f2<<sLine<<endl;
              sLast= sLine;
            }

          f2.close();
        }
      f1.close();
      if(Exist(sftmp))
        {
          DeleteFile (f );
          MoveFile (sftmp,f );
        }
    }
  return 0;
}




James

  •  

Frederick J. Harris

You know, something else occurred to me too.  Not sure if I'll take the time to investigate this further, but in principal, I don't see any reason all the various ranks, i.e., 2 D, 3 D, and 4 D (5 D and 6 D and 7 D, etc. if you would want it), couldn't be implemented within just one class with all the various Constructors overloaded, and that would be true too for the actual functions that Get/Sleet the values at their correct indexes in the underlying storage.  Coupled with the ability to use any variable type due to the template implementation, that would make for a pretty cool functionality.  Maybe there would be problems implementing it, but off the top of my head it seems doable.  At that point one would have a full implementation of PowerBASIC's array capabilities.
  •  

Frederick J. Harris

Ahh!   I see C++11 has a String::stoi(), but older C++ doesn't.  I almost forgot about atoi()/atof(), etc.,  that's the C Library way of doing it.   
  •  

Frederick J. Harris

I can't believe I was going to ignore my idea above about incorporating all the dimensions within one templated class, i.e., two, three, and four dimensions.  It worked out beyond my wildest expectations!  Symetry is a powerful and wonderful concept, and one of the things that makes the universe interesting.  Check this out!


#include <cstdio>
#include <memory.h>


template <class datatype> class CArray
{
public:
CArray(int i) : pObjs(0)                         // One Dimensional Array Constructor
{
  this->pObjs=new datatype[i];
  d1=i, d2=0, d3=0, d4=0;
}

CArray(int i, int j) : pObjs(0)                  // Two Dimensional Array Constructor
{
  int iNumElements=i*j;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=0, d4=0;
}

CArray(int i, int j, int k) : pObjs(0)           // Three Dimensional Array Constructor
{
  int iNumElements=i*j*k;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=k, d4=0;
}

CArray(int i, int j, int k, int l) : pObjs(0)    // Four Dimensional Array Constructor
{
  int iNumElements=i*j*k*l;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=k, d4=l;
}

datatype& operator()(int i)                      // One Dimensional Accessor
{
  return pObjs[i];
}

datatype& operator()(int i, int j)               // Two Dimensional Accessor
{
  return pObjs[i*d2 + j];
}

datatype& operator()(int i, int j, int k)        // Three Dimensional Accessor
{
  return pObjs[i*d2 + j + k*d1*d2];
}

datatype& operator()(int i, int j, int k, int l) // Four Dimensional Accessor
{
  return pObjs[i*d2 + j + k*d1*d2 + l*d1*d2*d3];
}

bool blnMemoryIsGood()
{
  return !!pObjs;
}

int UBound(int iDim)
{
  if(iDim==1)
     return d1;
  if(iDim==2)
     return d2;
  if(iDim==3)
     return d3;
  if(iDim==4)
     return d4;
  else
     return 0;
}

~CArray()
{
  delete [] this->pObjs;
}

private:
datatype* pObjs;
int d1;
int d2;
int d3;
int d4;
};


int main(void)
{
int c=4,r=3,h=4,dim_4=2;
CArray<int> ar_1(r);
CArray<int> ar_2(r,c);
CArray<int> ar_3(r,c,h);
CArray<int> ar_4(r,c,h,dim_4);

if(ar_1.blnMemoryIsGood())
{
    printf("ar_1() Allocated OK!\n");
    printf("ar_1.UBound(1) = %d\n\n",ar_1.UBound(1));
}
if(ar_2.blnMemoryIsGood())
{
    printf("ar_2() Allocated OK!\n");
    printf("ar_2.UBound(1) = %d\n",ar_2.UBound(1));
    printf("ar_2.UBound(2) = %d\n\n",ar_2.UBound(2));
}
if(ar_3.blnMemoryIsGood())
{
    printf("ar_3() Allocated OK!\n");
    printf("ar_3.UBound(1) = %d\n",ar_3.UBound(1));
    printf("ar_3.UBound(2) = %d\n",ar_3.UBound(2));
    printf("ar_3.UBound(3) = %d\n\n",ar_3.UBound(3));
}
if(ar_4.blnMemoryIsGood())
{
    printf("ar_4() Allocated OK!\n");
    printf("ar_4.UBound(1) = %d\n",ar_4.UBound(1));
    printf("ar_4.UBound(2) = %d\n",ar_4.UBound(2));
    printf("ar_4.UBound(3) = %d\n",ar_4.UBound(3));
    printf("ar_4.UBound(4) = %d\n",ar_4.UBound(4));
}
getchar();

return 0;
}

/*
ar_1() Allocated OK!
ar_1.UBound(1) = 3

ar_2() Allocated OK!
ar_2.UBound(1) = 3
ar_2.UBound(2) = 4

ar_3() Allocated OK!
ar_3.UBound(1) = 3
ar_3.UBound(2) = 4
ar_3.UBound(3) = 4

ar_4() Allocated OK!
ar_4.UBound(1) = 3
ar_4.UBound(2) = 4
ar_4.UBound(3) = 4
ar_4.UBound(4) = 2
*/


The one dimensional case simply falls out of it as something of a trieval special case.
  •  

Frederick J. Harris

I've tested this thing more and can find no faults with it.  Here is my test program.  It creates four CArrays; 1st one is a one dimensional array of CBox objects, i.e., just a simple class object encapsulating a box with len, width, and ht - and a volume - the simple class for that is at top; 2nd is a 2D array of std::strings; 3rd is a 3D array of doubles; 4th is a 4D array of ints.  These are all output from an output function.  That shows the pretty wierd syntax for functions that take templated parameters.  The output is after te program.  So you can see what I spent my day doing, huh? 


#include <cstdlib>
#include <cstdio>
#include <string>
#include <memory.h>
#include <time.h>


class CBox
{
public:
CBox()
{
  m_Length=0, m_Width=0, m_Height=0;
}

CBox(int iLen, int iWth, int iHt)
{
  m_Length=iLen, m_Width=iWth, m_Height=iHt;
}

int Volume()
{
  return m_Length*m_Width*m_Height;
}

private:
int m_Length;
int m_Width;
int m_Height;
};


template <class datatype> class CArray
{
public:
CArray(int i) : pObjs(0)                         // One Dimensional Array Constructor
{
  this->pObjs=new datatype[i];
  d1=i, d2=0, d3=0, d4=0;
}

CArray(int i, int j) : pObjs(0)                  // Two Dimensional Array Constructor
{
  int iNumElements=i*j;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=0, d4=0;
}

CArray(int i, int j, int k) : pObjs(0)           // Three Dimensional Array Constructor
{
  int iNumElements=i*j*k;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=k, d4=0;
}

CArray(int i, int j, int k, int l) : pObjs(0)    // Four Dimensional Array Constructor
{
  int iNumElements=i*j*k*l;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=k, d4=l;
}

datatype& operator()(int i)                      // One Dimensional Accessor
{
  return pObjs[i];
}

datatype& operator()(int i, int j)               // Two Dimensional Accessor
{
  return pObjs[i*d2 + j];
}

datatype& operator()(int i, int j, int k)        // Three Dimensional Accessor
{
  return pObjs[i*d2 + j + k*d1*d2];
}

datatype& operator()(int i, int j, int k, int l) // Four Dimensional Accessor
{
  return pObjs[i*d2 + j + k*d1*d2 + l*d1*d2*d3];
}

bool blnMemoryIsGood()
{
  return !!pObjs;
}

int UBound(int iDim)
{
  if(iDim==1)
     return d1;
  if(iDim==2)
     return d2;
  if(iDim==3)
     return d3;
  if(iDim==4)
     return d4;
  else
     return 0;
}

~CArray()
{
  delete [] this->pObjs;
}

private:
datatype* pObjs;
int d1;
int d2;
int d3;
int d4;
};


template<typename t1, typename t2, typename t3, typename t4> void Output(CArray<t1>& Ar1, CArray<t2>& Ar2, CArray<t3>& Ar3, CArray<t4>& Ar4)
{
int i,j,k,l;

for(i=0; i<Ar1.UBound(1); i++)
     printf("Ar1(%d).Volume() = %d\n",i,Ar1(i).Volume());
printf("\n\n");

for(i=0; i<Ar2.UBound(1); i++)
{
     for(j=0; j<Ar2.UBound(2); j++)
     {
         printf("%s\t",Ar2(i,j).c_str());
     }
     printf("\n");
}
printf("\n\n");

for(k=0; k<Ar3.UBound(3); k++)
{
     for(i=0; i<Ar3.UBound(1); i++)
     {
         for(j=0; j<Ar3.UBound(2); j++)
             printf("%8.2f\t",Ar3(i,j,k));
         printf("\n");
     }
     printf("\n");
}
printf("\n\n");

for(l=0; l<Ar4.UBound(4); l++)
{
     for(k=0; k<Ar4.UBound(3); k++)
     {
         for(i=0; i<Ar4.UBound(1); i++)
         {
             for(j=0; j<Ar4.UBound(2); j++)
                 printf("%d\t",Ar4(i,j,k,l));
             printf("\n");
         }
         printf("\n");
     }
}
}


int main(void)
{
int c=4,r=3,h=4,dim_4=2,iCtr=0;
CArray<CBox> ar_1(r);
CArray<std::string> ar_2(r,c);
CArray<double> ar_3(r,c,h);
CArray<int> ar_4(r,c,h,dim_4);

if(ar_1.blnMemoryIsGood())
{
    printf("ar_1() Allocated OK!\n");
    printf("ar_1.UBound(1) = %d\n\n",ar_1.UBound(1));
    ar_1(0)=CBox(1,2,3);
    ar_1(1)=CBox(2,3,4);
    ar_1(2)=CBox(3,4,5);
}
if(ar_2.blnMemoryIsGood())
{
    char szBuffer[16];
    printf("ar_2() Allocated OK!\n");
    printf("ar_2.UBound(1) = %d\n",ar_2.UBound(1));
    printf("ar_2.UBound(2) = %d\n\n",ar_2.UBound(2));
    for(int i=0; i<ar_2.UBound(1); i++)
    {
        for(int j=0; j<ar_2.UBound(2); j++)
        {
            ar_2(i,j)="(";
            sprintf(szBuffer,"%d",i);
            ar_2(i,j)=ar_2(i,j)+szBuffer+",";
            sprintf(szBuffer,"%d",j);
            ar_2(i,j)=ar_2(i,j)+szBuffer+")";
        }
    }
}
if(ar_3.blnMemoryIsGood())
{
    printf("ar_3() Allocated OK!\n");
    printf("ar_3.UBound(1) = %d\n",ar_3.UBound(1));
    printf("ar_3.UBound(2) = %d\n",ar_3.UBound(2));
    printf("ar_3.UBound(3) = %d\n\n",ar_3.UBound(3));
    srand((unsigned)time(NULL));
    for(int i=0; i<ar_3.UBound(3); i++)
    {
      for(int j=0; j<ar_3.UBound(1); j++)
      {
         for(int k=0; k<ar_3.UBound(2); k++)
             ar_3(j,k,i)=(double)rand()/3;
      }
    }
}
if(ar_4.blnMemoryIsGood())
{
    printf("ar_4() Allocated OK!\n");
    printf("ar_4.UBound(1) = %d\n",ar_4.UBound(1));
    printf("ar_4.UBound(2) = %d\n",ar_4.UBound(2));
    printf("ar_4.UBound(3) = %d\n",ar_4.UBound(3));
    printf("ar_4.UBound(4) = %d\n\n\n",ar_4.UBound(4));
    for(int l=0; l<ar_4.UBound(4); l++)
        for(int k=0; k<ar_4.UBound(3); k++)
            for(int i=0; i<ar_4.UBound(1); i++)
                for(int j=0; j<ar_4.UBound(2); j++)
                    ar_4(i,j,k,l)=iCtr++;
}
Output(ar_1,ar_2,ar_3,ar_4);
getchar();

return 0;
}


/*
ar_1() Allocated OK!
ar_1.UBound(1) = 3

ar_2() Allocated OK!
ar_2.UBound(1) = 3
ar_2.UBound(2) = 4

ar_3() Allocated OK!
ar_3.UBound(1) = 3
ar_3.UBound(2) = 4
ar_3.UBound(3) = 4

ar_4() Allocated OK!
ar_4.UBound(1) = 3
ar_4.UBound(2) = 4
ar_4.UBound(3) = 4
ar_4.UBound(4) = 2


Ar1(0).Volume() = 6
Ar1(1).Volume() = 24
Ar1(2).Volume() = 60


(0,0)   (0,1)   (0,2)   (0,3)
(1,0)   (1,1)   (1,2)   (1,3)
(2,0)   (2,1)   (2,2)   (2,3)


8255.67         7001.33         1385.33         4849.33
  789.67         2585.33         7970.00         4255.33
2202.33         2889.00         4975.00         6711.33

9064.33        10290.67         3533.67         5940.33
7999.00         3164.67         8025.67          987.00
3540.33         1131.67         7382.67         7292.67

8122.67         3607.67         7882.67         8823.00
1373.00         9530.33         8111.67         7244.33
2406.00         3907.67         7909.00         1912.67

  145.67         9841.33         6582.33         2057.00
5522.00         4294.00         5751.67         3229.33
3341.33         3775.00         4874.67         7550.67



0       1       2       3
4       5       6       7
8       9       10      11

12      13      14      15
16      17      18      19
20      21      22      23

24      25      26      27
28      29      30      31
32      33      34      35

36      37      38      39
40      41      42      43
44      45      46      47

48      49      50      51
52      53      54      55
56      57      58      59

60      61      62      63
64      65      66      67
68      69      70      71

72      73      74      75
76      77      78      79
80      81      82      83

84      85      86      87
88      89      90      91
92      93      94      95
*/
  •  

Frederick J. Harris

Just found an issue with the class that is easily fixed.  I think its illuminating to discuss.  This is really an old issue that anyone who inter-operates between C family languages and basic family languages is familiar with.  It concerns variable initialization.  If you recall in some of the earlier posts here I had used the C runtime memset() to zero out the memory returned by the C++ new operator.  In the forestry example I'm working with as I develop this multi-dimensional array capability, I found I had to do that or else I'd end up with junk values in the types of tables I'd presented several posts up.  The new operator returns valid memory, but leaves in place whatever junk was there from its last de-allocated use.  Now, that statement I just made isn't exactly right.  It seems to be only true with simple variable types such as ints, doubles, etc.  In the case of classes - and recall in my example above I created four different types of objects, i.e., a one dim CBox array, a two dim std::string array, a three dim double array, and a four dim  int array, when the new operator is called, it calls the constructor on the object being specified for however many objects are needed.  Think of what happens with std::string.  Even though NULL strings are being created, somewhere in the code for the class a pointer is being allocated, which will be some valid non-zero address, and the value at that address set to zero.  Now, if you go zeroing out all those array elements with memset(pObj, 0, some_number_of_bytes), you'll be erasing all those pointer addresses, and you'll get a GPF for sure.  If not immediately, then when the destructor for all those objects is called. 

This only leaves one reasonable alternative as I see it.  And that is to add another simple member function to the templated class to clear the memory when needed, and the user of the class is going to have to decide when he/she wants to call it.  So the way I see it being used (this has worked for me), is that after you instantiate a class of some type that needs zeroing out, such as an int or double, and after you've tested to make sure you have a good memory allocation, you'll want to call the CArray::ClearMemory() member.  So here's what it looks like now ...


template <class datatype> class CArray
{
public:
CArray(int i) : pObjs(0)                         // One Dimensional Array Constructor
{
  this->pObjs=new datatype[i];
  d1=i, d2=0, d3=0, d4=0;
  iNumObjects=i;
}

CArray(int i, int j) : pObjs(0)                  // Two Dimensional Array Constructor
{
  int iNumElements=i*j;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=0, d4=0;
  iNumObjects=i*j;
}

CArray(int i, int j, int k) : pObjs(0)           // Three Dimensional Array Constructor
{
  int iNumElements=i*j*k;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=k, d4=0;
  iNumObjects=i*j*k;
}

CArray(int i, int j, int k, int l) : pObjs(0)    // Four Dimensional Array Constructor
{
  int iNumElements=i*j*k*l;
  this->pObjs=new datatype[iNumElements];
  d1=i, d2=j, d3=k, d4=l;
  iNumObjects=i*j*k*l;
}

datatype& operator()(int i)                      // One Dimensional Accessor
{
  return pObjs[i];
}

datatype& operator()(int i, int j)               // Two Dimensional Accessor
{
  return pObjs[i*d2 + j];
}

datatype& operator()(int i, int j, int k)        // Three Dimensional Accessor
{
  return pObjs[i*d2 + j + k*d1*d2];
}

datatype& operator()(int i, int j, int k, int l) // Four Dimensional Accessor
{
  return pObjs[i*d2 + j + k*d1*d2 + l*d1*d2*d3];
}

bool blnMemoryIsGood()
{
  return !!pObjs;
}

int UBound(int iDim)
{
  if(iDim==1)
     return d1;
  if(iDim==2)
     return d2;
  if(iDim==3)
     return d3;
  if(iDim==4)
     return d4;
  else
     return 0;
}

void ClearMemory()   // Non-class types like ints, doubles, etc., need to be initialized
{                    // to zero.  This must not be doe for class types.
  memset(this->pObjs, 0, sizeof(datatype)*this->iNumObjects);
}

~CArray()
{
  delete [] this->pObjs;
}

private:
int       iNumObjects; // We'll need this to zero memory for non-class types
datatype* pObjs;       // pointer to the base memory allocation for array
int       d1;          // Dimension #1
int       d2;          // Dimension #2
int       d3;          // Dimension #3
int       d4;          // Dimension #4

};
  •