Page 1 of 1

Pointer to 2D array of objects (C++)

Posted: Thu Jun 28, 2007 11:41 pm
by AndrewAPrice
I'm writing a game with tile based map. The map can contain a dynamic number of tiles, each Tile being its own object (it contains info about what sort of tile it is along with a 3D mesh).

In my Tile Manager class (a class which handles all the map tiles) I have made a pointer to a 2D array of Tile objects:

Code: Select all

Tile **m_tiles;
How can I allocate this pointer and the tile objects?

I have thought of doing something like:

Code: Select all

	Tile tiles[m_width][m_height];

	for(int x = 0; x < m_width; x++)
		for(int y = 0; y < m_height; y++)
		{
			tile = new Tile(x,y,TileType::Grass);
		}

	m_tiles = &Tile;
But, I get the following errors:

Code: Select all

1>.\tilemanager.cpp(21) : error C2057: expected constant expression
1>.\tilemanager.cpp(21) : error C2466: cannot allocate an array of constant size 0
1>.\tilemanager.cpp(21) : error C2057: expected constant expression
1>.\tilemanager.cpp(21) : error C2466: cannot allocate an array of constant size 0
1>.\tilemanager.cpp(21) : error C2087: 'tiles' : missing subscript
1>.\tilemanager.cpp(21) : error C2133: 'tiles' : unknown size
1>.\tilemanager.cpp(21) : error C2512: 'Tile' : no appropriate default constructor available
I'm going to use a linked list, but if any one knows how to create a pointer to a 2D array I would be greatfull.

A 2D array would be faster - I could look up a tile by using m_tile[x][y] rather than doing thousands of searches through a linked list (the map size could but up to 100x100 tiles).

Posted: Fri Jun 29, 2007 9:06 am
by Colonel Kernel
C++ doesn't support 2D arrays directly in the language, unless the sizes of both dimensions are known at compile-time. For dynamically-sized 2D arrays you sort of have to "fake it" using an array of arrays, or a 1D array where you calculate the array index from your two "2D" indices. I think there might be a way to make this prettier via operator overloading, but it's been a while...

First, I will explain why the compiler is complaining (you learn a lot by understanding compiler diagnostics):

Code: Select all

   // You can't do this because m_width and m_height are variables.
   // The syntax you're using is telling the compiler to create a fixed-size
   // 2D array on the stack, but it can't be fixed-size because m_width
   // and m_height are variables.
   // Also, I hope you have a default constructor defined for Tile, because
   // this is declared as an array of Tile objects, not pointers to Tile
   // objects.
   Tile tiles[m_width][m_height]; 

   for(int x = 0; x < m_width; x++) 
      for(int y = 0; y < m_height; y++) 
      { 
         // I don't see the variable "tile" declared anywhere. I hope it's
         // a Tile*, because otherwise this won't work.
         tile = new Tile(x,y,TileType::Grass); 
      } 

   // You can't take the address of a type. If you meant &tiles, that's
   // a fairly dangerous thing to do because stack-allocated objects
   // disappear as soon as the function in which they are declared exits.
   m_tiles = &Tile; 
For the array of arrays approach, try this (warning: I haven't tried compiling it, so there may be a little error or two):

Code: Select all

    // Assuming that m_width and m_height are both of type int, and
    // that m_tiles is of type Tile***. There are three stars because
    // it is a 2D array of pointers to Tile objects.
    m_tiles = new Tile**[m_width]; // Create the array of arrays.
    for(int x = 0; x < m_width; x++)
    {
        m_tiles[x] = new Tile*[m_height]; // Create each "column"

        for(int y = 0; y < m_height; y++) 
        { 
            // This is why m_tiles must be Tile*** -- because operator new
            // returns a Tile*, not a Tile.
            m_tiles[x][y] = new Tile( x, y, TileType::Grass ); 
        } 
    }
Don't forget to delete everything when you're done with it. If you're doing this all manually just to learn the language, that's fine, but in reality a better way to do this would be to use STL vectors (this code is also untested):

Code: Select all

    // Assuming that m_width and m_height are both of type int, and
    // that m_tiles is of type std::vector< std::vector<Tile*> >.

    // You can also construct it to be the right size if you use an
    // initialization list in your constructor.
    m_tiles.resize( m_width ); // This creates each "column" for you.
    for(int x = 0; x < m_width; x++)
    {
        for(int y = 0; y < m_height; y++) 
        { 
            m_tiles[x].push_back( new Tile( x, y, TileType::Grass ) );
        } 
    }
You still need to delete each Tile in the vector-of-vectors when you're done using it, however. But it's a lot less to clean up than when using plain arrays.

Posted: Fri Jun 29, 2007 1:05 pm
by Kevin McGuire
In my Tile Manager class (a class which handles all the map tiles) I have made a pointer to a 2D array of Tile objects:
What in the world is going on here... I am not going to build a spaceship just to go a couple of meters in distance.
Tile **tiles;
Why do you need a pointer (or an array of pointers) to a pointer (or an array of pointers)?
Tile <pointer><pointer>tiles;

Watch:
mov eax, offset tiles
mov eax, dword [eax] // operation for each dereference of a pointer
mov eax, dword [eax] // ... second dereference.


All you need is:
Tile *m_tiles;
m_tiles = new Tile[m_width * m_height];
m_tiles[myX + myY * m_width]


You should actually save cycles since you need no memory fetch for calculating the index as you would when using a pointer to an array of pointers. <-- No expert here..

Posted: Fri Jun 29, 2007 2:26 pm
by Colonel Kernel
Kevin McGuire wrote:
In my Tile Manager class (a class which handles all the map tiles) I have made a pointer to a 2D array of Tile objects:
What in the world is going on here... I am not going to build a spaceship just to go a couple of meters in distance.
Tile **tiles;
Why do you need a pointer (or an array of pointers) to a pointer (or an array of pointers)?
Tile <pointer><pointer>tiles;

Watch:
mov eax, offset tiles
mov eax, dword [eax] // operation for each dereference of a pointer
mov eax, dword [eax] // ... second dereference.


All you need is:
Tile *m_tiles;
m_tiles = new Tile[m_width * m_height];
m_tiles[myX + myY * m_width]


You should actually save cycles since you need no memory fetch for calculating the index as you would when using a pointer to an array of pointers. <-- No expert here..
I said exactly this in far fewer words already:
For dynamically-sized 2D arrays you sort of have to "fake it" using an array of arrays, or a 1D array where you calculate the array index from your two "2D" indices.
But yes, that's probably the most efficient way to deal with it.

Posted: Fri Jun 29, 2007 2:39 pm
by Kevin McGuire
Talking about another thread where you did the exact same thing to me.
I know you did. :D But look at this post by hckr83, then scroll up to yours and then to mine. You will notice I already stated the answer in that thread, which you then posted afterwards (after mine).
http://www.osdev.org/phpBB2/viewtopic.p ... ght=#98993

Talking about this thread.
If I had noticed that I would have placed your words in quotes above mine to signify that you had indeed suggested a more optimal solution, but I would not have removed my post because it clearly and cleanly states and shows the optimal solution to the problem - very much like the thread above where I posted a bunch of garbage code then you came afterwards and posted a clean, elegant, and simple worded solution.

Sorry. :P

Posted: Fri Jun 29, 2007 3:35 pm
by Colonel Kernel
Kevin McGuire wrote:Sorry. :P
No worries. I assumed that the OP was actually interested in learning about multi-dimensional arrays in C++, not necessarily in finding the most optimal solution. I'm probably wrong on that point. :)

Something to watch out for though -- if you find yourself explaining things in code instead of English, it's usually a warning sign that you may not understand it enough yourself (or have not internalized it and abstracted it enough in your own mind).

Posted: Fri Jun 29, 2007 4:39 pm
by Kevin McGuire
I agree with you completely, and I feel I really need to explain more in plain English rather than giving examples in code since this is a sign that I do not completely understand what I am trying to do.

I will spend more time learning about what I am doing in a attempt to improve myself ever further. I appreciate the suggestion and constructive criticism. I really do appreciate your time and patience with me. It is something that was in no way required.

I will become even better as you have given me the desire to exceed my own intellectual limits.