Passing multidimensoinal arrays to class constructors.

Member
Posts: 100
Joined: 2006.05
Post: #1
I've been playing with this for awhile now but I can't get anything to even compile. It keeps giving me incompatible type errors.

Code:
class Model
{
private:
    float vertices[][3];
    int indices[][3];
.
.
.
Model::Model(float verts[][3], int inds[][3], int n)
{
    vertices = verts; indices = inds; nIndices = n;
}

Code:
Model.cpp:15: error: incompatible types in assignment of 'int (*)[3]' to 'int [0u][3]'
Model.cpp:15: error: incompatible types in assignment of 'float (*)[3]' to 'float [0u][3]'

I do not know the dimensions of verts or inds upon receiving them...
Quote this message in a reply
Member
Posts: 245
Joined: 2005.11
Post: #2
You'll have to use float** and int** types and dynamically allocate storage for the data. The way you are declaring arrays is only valid if you assign values at the same time, as in:
Code:
int indices[][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
Quote this message in a reply
Member
Posts: 100
Joined: 2006.05
Post: #3
I tried that, but I get an EXC_BAD_ACCESS error when I do that. For example:

Code:
int a[][2] = { {2, 3}, {4, 5} };
int **b = (int **)a; // Error if I don't typecast
cout << b[0]<< "\n" << a[0];

outputs

Code:
0x2
0xbffff98c

it is no wonder I get an error if I try b[0][0]...
Quote this message in a reply
Apprentice
Posts: 19
Joined: 2007.03
Post: #4
For future reference, multidimensional arrays in C/C++ are almost always a headache unless they are staticly allocated.

Try using a single dimensional array of VERTEX structures that have x,y,z plus any normal information. The same goes for triangle indices.
Quote this message in a reply
Moderator
Posts: 373
Joined: 2006.08
Post: #5
Code:
int** a = new int*[2];
a[0] = new int[2];
a[1] = new int[2];
a[0][0] = 2;
a[0][1] = 3;
a[1][0] = 4;
a[1][1] = 5;

works fine, and you can go:

Code:
int** a = new int*[2];
a[0] = new int[2];
a[1] = new int[2];
a[0][0] = 2;
a[0][1] = 3;
a[1][0] = 4;
a[1][1] = 5;

float** b= new float*[2];
b[0] = new float[2];
b[1] = new float[2];
b[0][0] = 2.6;
b[0][1] = 6.7;
b[1][0] = 3.8;
b[1][1] = 9.3;

that code will compile, but what exactly are you trying to do with your constructor code? Copy all of the values of the arrays? If so, you'd need to pass the size of the arrays with it, so something like this:

Code:
class Model
{
private:
    float** vertices;
    int** indices;
.
.
.
Model::Model(float[][] verts, int[][] inds, int n, int x)
{
    vertices = new float*[n];
             indices = new int*[n];
for(int i=0; i<n; i++)
{
vertices[i] = new float[x];
indices[i] = new int[x];
for(int y=0; y<x; y++)
{
vertices[i][y] = verts[i][y];
indices[i][y] = inds[i][y];
}
}
}

and then pass the values like so:
Code:
Model theModel = new Model(b, a, 2, 2);

good luck man...I'd read up on dynamic memory allocation a bit; it's quite tricky for the beginner Smile
-wyrmmage

Worlds at War (Current Project) - http://www.awkward-games.com/forum/
Quote this message in a reply
Member
Posts: 100
Joined: 2006.05
Post: #6
Aressera Wrote:For future reference, multidimensional arrays in C/C++ are almost always a headache unless they are staticly allocated.

Try using a single dimensional array of VERTEX structures that have x,y,z plus any normal information. The same goes for triangle indices.

Bah. I can see why... I was going to resort to using STL containers but I wanted to avoid them for simplicity...

I also wanted to see if I could get away with just passing the pointers and using the objects stored in memory, but I guess keeping a copy would be more practical.
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #7
The problem is it isn't really a double pointer. Statically allocated multi-dimensional arrays are really flat arrays with syntax sugar for multiple dimensions. For example,
Code:
int bla[3][3] = {/* some initializer */};
int bla12 = bla[1][2];
is equivalent to
Code:
int bla[9] = {/* some initializer */};
int bla12 = bla[1*3 + 2];

The problem is they're the same in memory (and most likely the same in assembly), but their types are slightly different to the compiler. Moral: don't use multi-dimensional arrays in parameters unless it's meant to be used only with typedefed multidimensional arrays. (for example: typedef float Matrix[4][4];, then use Matrix everywhere instead) Also, you can't cast to array types, so you can't really get around it. Just have the functions take regular pointers and index the multiple dimensions yourself.

Edit: oh, and don't take wyrmmage's advise. Dynamically allocating multiple levels of pointers is difficult to manage, and you shouldn't use it unless you absolutely must. The best thing is to use everything in a flat array and do the "multidimensional" indexing yourself. The only time you should do something like wyrmmage is doing is when the multiple dimensions have different sizes. (for example, for a 2D float array, you want the first row to have 5 columns, and the second row to have 8 columns) However, if you need that, you probably should also see if there's an easier way to do it before you start calling 500 news everywhere.
Quote this message in a reply
Member
Posts: 100
Joined: 2006.05
Post: #8
I thought it was an array of pointers only because the call a[0] returned a pointer value...
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #9
For a multidimensional array, each of the "dimensions" is just an offset into the pointer. Each index in a dimension simply offsets the pointer for all the following dimensions for that index number of times. For example, for an array int bla[3][3], bla [2] would offset the pointer by 3*2, since it's skipping the second dimension (which has 3 elements) 2 times. However, it still doesn't know exactly where in the array to dereference, since you haven't given an index for the second dimension, so it just returns a pointer.

Similarly, for a 3D array int bla2[4][3][4], bla2[2] offsets the array by (3*4)*2, since it's offsetting by the second and third dimensions, which have 3 and 4 elements respectively. Similarly, if you use bla2[2][1], it will be offset by (3*4)*2 + 4*1, since it's offsetting for the first and second dimensions. However, in both cases, it still returns a pointer since it doesn't know exactly where the element you want is, because you need to have the last dimension specified. If you were to specify bla2[2][1][3], the final offset into the array would be (3*4)*2 + 4*1 + 3, and it would then return the value since you have the final location in the array.

To summarize, multidimensional arrays are still simply flat locations in memory. The only difference is the compiler adds syntax sugar for accessing the multiple dimensions of the array by doing the math for you when adding the offsets to the original pointer to look into the various parts of the array. However, when you don't specify all the dimensions of an array when indexing, it returns a pointer for the offset it could calculate so far, since you haven't specified all of the dimensions yet.
Quote this message in a reply
Moderator
Posts: 373
Joined: 2006.08
Post: #10
akb825 is certainly right; you shouldn't go my route unless you are using different sizes of dynamic arrays in the second layer of the two-dimensional array, however, it looked like you might be planning on doing so, so I provided the code Wink
keeping a reference to memory would be a good bit easier in your case, as long as you don't ever need to alter the values inside of the arrays that you're passing Grin
For example, consider the code you would need just to keep a reference to the array you passed:
Code:
class Model
{
private:
    float* vertices;
    int* indices;
.
.
.
Model::Model(float* verts, int* inds, int n, int x)
//note that that *might* need to be: Model::Model(float*** verts, int*** inds, int n, int x)  but I'm pretty sure that the first one is right.
{
vertices = verts;
indices = inds;

//not that you can now access the elements of the array like so:

float zeroOne = (*vertices)[0][1];
int oneOne = (*indices)[1][1];

}

then you merely call it like this:
Code:
int** a = new int*[2];
a[0] = new int[2];
a[1] = new int[2];
a[0][0] = 2;
a[0][1] = 3;
a[1][0] = 4;
a[1][1] = 5;

float** b= new float*[2];
b[0] = new float[2];
b[1] = new float[2];
b[0][0] = 2.6;
b[0][1] = 6.7;
b[1][0] = 3.8;
b[1][1] = 9.3;

Model theModel(&b, &a, 2, 2);

Much cleaner and shorter code Wink
Note that, as akb said, you should only use my way of doing things if you need a dynamic array with a different number of elements in the second tier. (here's a small example):
Code:
float** b = new float*[2];
b[0] = new float[2];
b[1] = new float[1];
b[0][0] = 2.5;
b[0][1] = 5.6;
b[1][0] = 3.6;
Note that this can introduce new problems into your code, namely you would have to have an array containing the lengths of the second tier of values, and then pass it to the constructor; if you're interested in that, just ask and I'll go into more detail with some actual code Wink
-wyrmmage

Worlds at War (Current Project) - http://www.awkward-games.com/forum/
Quote this message in a reply
Member
Posts: 100
Joined: 2006.05
Post: #11
I went ahead and used an array with Vertex structures. I'll keep this in mind next time I need it though.
Quote this message in a reply
Member
Posts: 245
Joined: 2005.11
Post: #12
wyrmmage Wrote:Note that this can introduce new problems into your code, namely you would have to have an array containing the lengths of the second tier of values, and then pass it to the constructor; if you're interested in that, just ask and I'll go into more detail with some actual code

Personally, when it gets to that stage I prefer to keep data and metadata together inside structs
Code:
typedef struct {
            int numberOfBits;
            int *bits;
            } thingType;

int        numberOfThings;
thingType  *things;

Doing things this way make it much easier to keep track of what's going on, and allows you to forget the difference between static and dynamic multidimensional arrays. Shock

<EDIT>Why does it have to be so hard to make the indenting look tidy? Mad </EDIT>
<edit2>Since you're in C++ you could replace the struct with another class and have it look after its own memory a bit more, which may help to avoid memory leaks. Just a thought.</edit2>
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Weird problem passing integer variables.. quarus 6 4,942 Mar 15, 2009 12:47 PM
Last Post: quarus