2

I've looked around trying to figure out if I should return a 2D array in C++, but got mixed answers.

Some of the answers say no because the data is local to the function and when it returns, the array points to "junk data" (as it can be overwritten at any time). However, some said "yes, return it like a normal array."

So, to my dilemma:

I have a 2D array that holds pointers to Tile objects

Tile* map[128][128];

Should I return the array in a function? Why or why not? If the answer is yes, how would I do that?

EDIT: I was unclear. I want to make a getter method to return the map variable and be able to use the pointers in the array in another function.

  • If you're creating the objects in the function, then yes, you need to dynamically allocate them if you don't want dangling pointers. – chris Dec 11 '12 at 01:59
  • @chris The array is part of a class. I clean it up properly. –  Dec 11 '12 at 02:00
  • You can't return arrays from a function anyway, it's not allowed by the language. Can you be a bit more specific what the situation is? – Daniel Fischer Dec 11 '12 at 02:00
  • @CodingMash No, it's an array of arrays of pointers, commonly an array of arrays is called a 2D array. – Daniel Fischer Dec 11 '12 at 02:02
  • @CodingMash ummm, not its not – Keith Nicholas Dec 11 '12 at 02:02
  • @DanielFischer I'm trying to make a simple game that has `Tile` pointers stored in the `map` 2D array. I want to return the Tiles to be able to render them to a screen. –  Dec 11 '12 at 02:05
  • So `map` is a class member, and not an array local to a function? And wouldn't you _pass_ the array to the renderer rather than returning it from a function? That can be done. – Daniel Fischer Dec 11 '12 at 02:10
  • I have a `TileManager` object which is used by the `Renderer` object. I want to call `tm.getTiles()` (or something similar) in the `Renderer` so I can render the Tiles. –  Dec 11 '12 at 02:12

3 Answers3

4

You could do that... the problem is that your caller is now responsible for freeing the memory. And he might not know how you allocated it. Should he call free()? delete[]? A free routine provided by the OS? Or one provided by some other memory caching system in your app?

Two common ways around this are:

  • Have the caller allocate the memory and your function merely populates it
  • Use a C++ array class; here you might have a std::vector containing a std::vector of Tile*. This is great as it absolves you of manually dealing with the memory allocation/deallocation.

But then... who's going to free the Tile* instances? And with what API? So perhaps you need a vector of vectors of Tile, rather than Tile*.

vector<vector<Tile>> map;
Graham Perks
  • 23,007
  • 8
  • 61
  • 83
  • Wait, the caller is responsible for freeing the memory? The `map` variable is inside of a class, can the class not call internal methods to `delete` the pointers? –  Dec 11 '12 at 02:06
  • If that's the case, then when are you trying to return it from a function? Is this simply a getter method? Sorry, I thought this was a function to generate the map. In that case have your function return type be Tile**. e.g. Tile** getMap() { return map; } – Graham Perks Dec 11 '12 at 02:14
  • `smart_ptr` or `unique_ptr` would also be very useful in this situation. – Matt Kline Dec 11 '12 at 02:16
  • @GrahamPerks Yea, this is just a getter method. @slavik262 I don't know what `smart_ptr` or `unique_ptr` are; I'll research that some. –  Dec 11 '12 at 02:16
  • @GrahamPerks Compiling under GCC, it says `Tile**` is not the correct type for `map`. It says `map` is of the type `Tile* (*)[128]`. How would I return it then? –  Dec 11 '12 at 02:45
  • You should mention the third common way: For every function that allocates a resource, provide a matching one that frees it. C++ class using RAII is the OO version of this. – Keith Dec 11 '12 at 02:48
  • I don't have a compiler handy... try Tile* (*getMap())[128] { return map; } – Graham Perks Dec 11 '12 at 03:12
  • @Ken Gah, I meant `shared_ptr`. `shared_ptr` and `unique_ptr` are parts of the new C++11 standard. They're pointers that delete whatever they are pointing to when they fall out of scope, solving a lot of these ownwership questions. – Matt Kline Dec 11 '12 at 15:22
3

The 2 answers you are getting is due to the two different ways this is possible.

Stack based - local memory, deallocated and the pointer returned is invalid

typedef int *(*TileGrid)[128];
Tile* map[128][128];
....
TileGrid GetTiles(){
    return map;
}

Heap Allocated - allocated on heap and not deallocated after the function ends, up to caller of this function to deallocate - safe.

typedef int *(*TileGrid)[128];
TileGrid map = new Tile*[128][128];
....
TileGrid GetTiles(){
    return map;
}

Recommended - C++ way - array instead of vector since size is compile time constant (true C++ way would be to use std::unique_ptr<Tile> instead of Tile*, but 1 issue at a time. These containers automatically manage the memory for you, and std::array can be used as if it is a regular array.

typedef std::array<std::array<Tile*,128>,128> TileGrid;
TileGrid map ;
...
TileGrid& GetTiles(){
    return map;
}

Return by reference to prevent an expensive copy, and because the function is just a getter and the object maintains ownership of the map.

Edit: fixed for a getter function

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Karthik T
  • 31,456
  • 5
  • 68
  • 87
  • You're talking about returning 64 kilobyte objects, which can't take advantage of move semantics, by value. You'll be saved in many cases by copy elision, but I think vector would be better here, since it can take advantage of move semantics when copy elision fails. – Benjamin Lindley Dec 11 '12 at 02:24
  • I havent read up fully on the new move semantics, alternatively we could also pass in TileGrid as a reference. – Karthik T Dec 11 '12 at 02:27
  • looks like OP is writing a getter, not a generator, In that case returning a reference would be my choice – Karthik T Dec 11 '12 at 02:29
  • Both your "two ways" are not valid and do not compile – newacct Dec 11 '12 at 02:37
  • wow syntax gets [involved](http://stackoverflow.com/a/10116675/1520364), not even sure how to return without a typedef.. fixed.. just use `std::array`! – Karthik T Dec 11 '12 at 03:05
1

C/C++ has two types of memory - stack and heap. Usually variables are allocated on the stack which is temporary storage and is marked for re-use whenever you exit the current scope - like when you end a loop or return from a function.

When you use new it will allocate heap memory which will stay around until you use delete but you have to be careful that you match up all the new and delete calls so that you don't get a memory leak. There are many strategies for managing this situation in C++. You can google for 'memory management in C++' to learn more.

In your simple case, I would recommend avoiding pointers but using an stl container to build your array of tiles. For example a std::vector like vector< vector<Tile> > map. You can use the same square brackets syntax to access the map e.g. map[x][y] = Tile(); That way the memory allocation and management of the array is handled in the vector class.

Matthew Smith
  • 6,165
  • 6
  • 34
  • 35
  • C++ does not say anything about a stack or a heap. It mentions storage specifiers but not how they are implemented. – bitmask Dec 11 '12 at 02:25