Changing class type of a vector element

Hello,

I am programming a 2-D platformer video game. The stages are composed of an array (really a vector) of 16x16 px^2 tiles. I have defined a base class "Tile" and several derived classes, e.g., "Ramp", "Door", etc., which have their own attributes. The idea is that upon entering a room, the program will load all of the necessary tile data for that room into a vector. So, I have a vector that looks like: vector <Tile*> room_tiles, and resize it based on the total number of tiles in the room: room_tiles.resize(Tile_Count). I then want to read in certain info from the data file containing all of the tile information for that room. For example, if the data file says Tile 5 should be a ramp, I want to change the 5th element of the room_tiles vector to the derived ramp class. This is really where I'm having trouble. I've worked with vectors of base and derived classes before, but those were always of indeterminate size and I always used something like: (Vector).push_back(new DerivedClass()) to specify the derived class of that element. The problem is that that method only seems to work if you are appending elements to the end of a vector.

Any suggestions on how I can do this? Also, suggestions for other ways of going about this would also be appreciated.

Thank you!
To answer your direct question, what you want is commonly known as the "Factory" pattern. IE: you need a function that takes some kind of identifying mark from the file and generates the appropriate type.


This is a very simplistic example:
1
2
3
4
5
6
7
8
9
10
11
12
13
Tile* TileFactory(istream& data)
{
    // read a character from a file
    char c = data.get();

    // determine what kind of tile it is
    switch(c)
    {
    case 'R':  return new Ramp();
    case 'D':  return new Door();
    // etc
    }
}



As for other suggestions:

1) Remember that any time you use new you have to use delete to clean up or you get leaks. So, instead of dealing with raw pointers where you have to be vigilant about deleting, I strongly recommend you use smart pointers which do the deleting automatically so you don't have to worry about it.

IE:

1
2
3
4
5
6
7
typedef std::unique_ptr<Tile>  TilePtr;

std::vector<TilePtr> room_tiles;  // instead of vector<Tile*>

room_tiles.resize(whatever);

room_tiles[x] = TilePtr( TileFactory(datafile) );


If you use unique_ptr, you never have to worry about deleting. And you never have to worry about leaks.




2) Don't use polymorphism just because you can. The goal with polymorphism is so that different but similar types can all be treated the same way. If you can treat all of your derived Tile classes as if they were 'Tile's (and not as if they were Ramps, Doors, etc)... then that's great and it's a perfect use of polymorphism.

However... if you are downcasting from a Tile* to a Ramp* or a Door*... then that defeats the entire point and you might want to reconsider your design.
Thank you for your quick response.

Your example for how to read from the data file is something like what I was going to do. The issue I'm having is not with reading what type of tile something should be from a data file, but actually making that vector element into that type. So for example, if the data file says "return new Ramp()", how do I then make the vector element (which starts out as a pointer to the base class Tile) into a Ramp?

For your other suggestions (which I very much appreciate, by the way):

I've heard that unique_ptr is a smart solution to this problem. For whatever reason my compiler didn't recognize it, even after I did some typedef like you have above in line 1. Ultimately, I wasn't that bothered by manually deleting my vector because I planned on doing so when changing rooms, anyways. Basically, when changing from room A to B I delete the vectors containing all the information for room A, and then create the new vector for room B by reading in B's data file. I'm sure that this is a really sloppy way of doing things, but I'm less concerned with elegance and efficiency right now than just getting things working.

Regarding comment two, I only really learned about polymorphism and inheritance about a week ago when I was trying to implement something. I'm still not entirely sure when it's appropriate. In this case, I thought it was appropriate because I want a single vector to contain all of the information about every tile, even those these tiles behave very differently. Here's my algorithm for processing hero movement, for example:


Move Hero to new position
Get tile ID of hero's new position
If (primary type of tile is BARRIER)
{
move character just outside of tile
}

If(primary type of tile is RAMP)
{
move y position of character to x*slope
}
....
and so on


Basically, even though the behavior of the different tiles are all radically different, I thought that they all needed to be contained in the same vector, which necessitated polymorphism. If you have a better suggestion I would love to hear it.

Thanks again!
So for example, if the data file says "return new Ramp()", how do I then make the vector element (which starts out as a pointer to the base class Tile) into a Ramp?


If you are talking about putting the Ramp into the vector... it's simple: you just assign it. A Ramp* is a Tile* because Ramp is derived from Tile:

1
2
3
vector<Tile*> foo;

foo.push_back( new Ramp ); // <- perfectly legal 




If you are talking about taking the Ramp out of the vector... then this is kind of what I was getting at with point 2. You wouldn't.

If you have to treat it like a Ramp.... that is.... if you can't treat it like a Tile... then you probably should not use polymorphism.

There are ways to do this... but doing so kind of defeats the entire point of using inheritance in the first place.

I've heard that unique_ptr is a smart solution to this problem. For whatever reason my compiler didn't recognize it, even after I did some typedef like you have above in line 1.


Did you #include <memory>?

If yes... and if you still don't have unique_ptr available.... how old is your compiler? unique_ptr is a C++11 addition... so if your compiler is 4+ years old it might not have it.

Ultimately, I wasn't that bothered by manually deleting my vector because I planned on doing so when changing rooms, anyways


To clarify... you're deleting the items in the vector and not the vector itself, right? ;P

That's fine and it'll work. It's just extra work and has potential holes (not exception safe)

Basically, even though the behavior of the different tiles are all radically different, I thought that they all needed to be contained in the same vector


Different behavior is fine for polymorphism.
Different interface is not. IE: how you interact with the classes.


For your situation, you could probably get away with throwing all this info in a basic struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Tile
{
    enum
    {
        Barrier,
        Ramp,
        Door
    } type;
    
    int slope;      // only used with Ramps
    //etc
    //etc
};


Then you don't need polymorphism... which means you also don't need dynamic allocation... and you can just do vector<Tile> and you don't have to deal with new/delete at all.

If you are talking about putting the Ramp into the vector... it's simple: you just assign it. A Ramp* is a Tile* because Ramp is derived from Tile:

1
2
3



vector<Tile*> foo;

foo.push_back( new Ramp ); // <- perfectly legal




I've done just that for vectors where I keep having to add new elements. For example, I have a vector for bullets and every time the character shoots his gun I do a .push_back(), adding a new bullet to the end of the vector. What I'm trying to do now is slightly different (and I don't think I'm explaining it well...). Here's what I want to do:


Enter new room

Determine total number of tiles in new room

Declare a vector of pointers to base class, called room_tiles, with total number of elements equal to the number of tiles determined above ( vector<Tile*>(total number of tiles) room_tiles)

Read tile data in from external data file
e.g., DataFile >> Tile 17 >> is type Ramp >> with slope 1/2 >> and with y-intercept 3
(note that I realize that I can't just do exactly as written above; I'd store each data member to a temporary variable for processing)

Set the vector item to have the above qualities:
room_tiles[17] should be of derived class Ramp, room_tiles[17]->Slope = 1/2, etc. etc.


Now the last step is the one I'm having trouble with. Is it as simple as room_tiles[17] = new Ramp(Initialization variables)?

Did you #include <memory>?


No I didn't (and now I feel stupid! :))

To clarify... you're deleting the items in the vector and not the vector itself, right? ;P


Yes, I would delete all of the items in the vector to free up memory.

The struct idea actually sounds pretty good. The one reservation I have is that some of the specialized tiles have shared behavior that I would like to contain in the base class. Specifically, the base class contains information about whether the tile blocks motion or not. So when checking whether a certain proposed move is allowed, I can say:


propose move to tile (x,y)
get tile at position (x,y)
if(room_tiles[ tile at (x,y) ]->primary_type == barrier)
{
move back to old position
}.


It seems like this method saves me from doing multiple if() statements during the movement processing, because rather than check all the types of tiles that block movement, I can just check to see if the tile at position (x,y) has the property that it blocks movement.
Now the last step is the one I'm having trouble with. Is it as simple as room_tiles[17] = new Ramp(Initialization variables)?


Yes. It is that simple. You can assign a Derived* to a Base*. Just not the other way around.
so...
1
2
3
4
5
6
7
// you could do this:
room_tiles[x] = new Ramp( foo, bar );

// or you could do this:
Ramp* foo = new Ramp;
foo->bar = baz;
room_tiles[x] = foo;


The struct idea actually sounds pretty good. The one reservation I have is that some of the specialized tiles have shared behavior that I would like to contain in the base class. Specifically, the base class contains information about whether the tile blocks motion or not.


The struct idea was assuming you needed to tell the difference between different tiles. Ideally you wouldn't need to do that.

Instead of saying what tiles are... why not just describe how they function? Does it really matter whether or not a tile is a ramp vs a barrier? Just assign every tile a slope variable and a 'blocking' variable and treat all tiles the same way regardless of what kind of tile it is. Then you don't need any if statement to check for the tile... you just check for the tile's behavior instead:

1
2
3
4
5
6
struct Tile
{
    int slope;
    bool blocking;
    // etc
};
The struct method just seems a bit inefficient, because only a very small subset of the total number of tiles in any given room will make use of the slope integer. Aside from that small gripe, I think that it may be easier to work with a single struct rather than a hierarchy of classes...

I think I can take it from here. Thanks for bothering to respond to all my questions and providing clear explanations! I really appreciate it.
The struct method just seems a bit inefficient, because only a very small subset of the total number of tiles in any given room will make use of the slope integer


It's not as inefficient as you think. ;)

I think I can take it from here. Thanks for bothering to respond to all my questions and providing clear explanations! I really appreciate it.


Happy to help. Good luck!
Topic archived. No new replies allowed.