Sign in to follow this  

[C++] Trying to work with abstraction and polymorphism

This topic is 3773 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Alright, here's the situation. I've got an abstract class Display_Object, which basically sets the interface for anything that can be drawn to the screen (classes such as Triangles, TriangleStrips, Quads, etc derive from it). Another abstract class, GamePiece, is derived from Display_Object. This defines the interface for game objects. Derived from that is another abstract class TilePiece, which defines squares on the gameboard that interact with other objects. The idea of all this is to be able to create new kinds of tiles at any time that have new ways of working with objects, without having to even touch the rest of the rendering code. I figure a good way to do this would be to have an array of pointers to TilePiece objects that are set to point to whatever tiles I come up with, so something like this:
TilePiece* tpGameTiles[NumberOfPossibleSpots];
tpGameTiles[DesiredSpot] = new SomeKindOfTile(...);
Obviously, the array elements should be initialized to NULL, and functions that iterate through it should check for NULL before trying to act upon it. My problem is when trying include this array as a member of another class (GameBoard in this case). I'd like to be able to load a TilePiece object into to the class and have it be added into the array without GameBoard having to know what kind of TilePiece it is, so that I can create new ones at will without having to mess with other parts of my code just to implement something that just ends up being a change from
new TilePiece1(...);
to
new TilePiece385(...);
How can I do this?

Share this post


Link to post
Share on other sites
you mean something like this:
void GameBoard::addTile(TilePiece addMe,int desiredSpot)
{
tpGameTiles[desiredSpot]=addMe;
}

...
//call
gb.addTile(new TilePiece1(), i);


Just make TilePiece1 be a child of TilePiece. Research inheritance.

Share this post


Link to post
Share on other sites
Quote:

Another abstract class, GamePiece, is derived from Display_Object. This defines the interface for game objects. Derived from that is another abstract class TilePiece, which defines squares on the gameboard that interact with other objects.


Not good. You might want to display the same game piece in multiple ways (for example, when you make a text mode for the game). Also, you don't want to recompile GamePiece every time you make change a to Display_Object that has nothing to do with game logic.

Quote:

My problem is when trying include this array as a member of another class (GameBoard in this case). I'd like to be able to load a TilePiece object into to the class and have it be added into the array without GameBoard having to know what kind of TilePiece it is,


Why would GameBoard need to know that? It sounds more like a "container" for pieces.
Of course *someone* needs to create the pieces and put them on the board.
But why should that "someone" be ignorant about which pieces they produce?

Share this post


Link to post
Share on other sites
Quote:
Original post by King Mir
you mean something like this:
void GameBoard::addTile(TilePiece addMe,int desiredSpot)
{
tpGameTiles[desiredSpot]=addMe;
}

...
//call
gb.addTile(new TilePiece1(), i);


Just make TilePiece1 be a child of TilePiece. Research inheritance.


All classes mentioned thus far except GameBoard are abstract. Therefore, I cannot make instantiate objects of that type. They are there to serve as interfaces. Therefore, I must work with pointers. I use abstract classes so that each object can control its own method of doing things (drawing, etc) while maintaining a common interface to use with functions that simply want an object with the designated interface. Furthermore, your method will have shearing- all the extra stuff will be lost because the base class copy constructor is called when using call by value. And when you assign the array element to the input element, even if it survived the first call by value (or you changed it to reference), it would be sheared again as it calls the base operator=().

Quote:
Original post by Barius
Not good. You might want to display the same game piece in multiple ways (for example, when you make a text mode for the game). Also, you don't want to recompile GamePiece every time you make change a to Display_Object that has nothing to do with game logic.


I see what you mean. Perhaps a better strategy would be to simply derive tiles I want to display graphically from both TilePiece AND Display_Object, while leaving GamePiece as not inheriting from anything?

Quote:
Why would GameBoard need to know that? It sounds more like a "container" for pieces.
Of course *someone* needs to create the pieces and put them on the board.
But why should that "someone" be ignorant about which pieces they produce?


It does other things as well, like displaying the board and all the pieces. Perhaps instead of trying to maintain its own copy, it should just accept a complete array externally? The reason it would need to know is if I wanted to add some tilepiece to the array, like
Board.AddTile(sometile);
. In this case, doesn't it need to know what type it is, rather than just knowing it's a TilePiece object?

For example:
void GameBoard::InsertTile(const TilePiece& tpIn, const unsigned int& nPosition)
{
TileArray[nPosition] = /* Insert Element Here */
}
Since TilePiece is abstract (and needs to be so each kind of tile can do its own thing as long as it conforms to the interface), I need to use pointers. To do that, I need to use new and delete. To use new and delete, I'd make a call like
new TilePiece(tpIn)
but that's not gonna work, since it's abstract, but I can't call new on the actual type without knowing what it is.

Share this post


Link to post
Share on other sites
The code that creates the types needs to know about them, but the code that uses them doesn't (this statement is a bit simplified but I think it's valid in your case).

Your game class stores an array of TilePiece pointers (BTW, you should really use std::vector instead, maybe in combination with boost:shared_ptr). It only knows that these pointers point to something that inherits from TilePiece and therefore it can call TilePiece's functions on it.

Assuming the function looks like this:


void GameBoard::add(TilePiece *tile) {
tiles[nextSlot++] = tile;

}


you can write:



Game game;
game.add(new DerivedTile1());
game.add(new DerivedTile2());


where DerivedTile1 and DerivedTile2 derive from TilePiece. Note that this doesn't call the copy c'tor or assignment operator becasue you're just assigning two pointers (and you can always assign a pointer to a derived class to a pointer to a base class. In fact, that's how polymorphism works).

As you can see, the code above needs to know about the derived types, but the Game class doesn't.

Quote:
Perhaps a better strategy would be to simply derive tiles I want to display graphically from both TilePiece AND Display_Object, while leaving GamePiece as not inheriting from anything?


You can make TilePiece contain an instance of DisplayObject. Then you can pass a reference to this instance to a class that's in charge of rendering, and you have (partially) decoupled your rendering code from your game logic (because the renderer class doesn't know anything about your game objects).

Share this post


Link to post
Share on other sites
Quote:
Original post by Andorien
I see what you mean. Perhaps a better strategy would be to simply derive tiles I want to display graphically from both TilePiece AND Display_Object, while leaving GamePiece as not inheriting from anything?


Possible - but it is really more flexible to not combine both logic and display into a single object.
(BTW, read Bob Martin's articles on OO design principles that are linked to in this post).
In your case - a board game - you could get by with a single integer "pictureNumber" in TilePiece that is used by the renderer as an index to an array of images, while a text mode renderer could use it as an index to a string of ASCII characters.

Quote:

It does other things as well, like displaying the board and all the pieces.


Again, it would be better to put the rendering code somewhere else, in a different object.

Quote:

For example:
void GameBoard::InsertTile(const TilePiece& tpIn, const unsigned int& nPosition)
{
TileArray[nPosition] = /* Insert Element Here */
}


Why the comment? You could just write
TileArray[nPosition] = &tpIn;

(I think this is what King Mir meant, he probably forgot the "&" in the parameter list).

Quote:

Since TilePiece is abstract (and needs to be so each kind of tile can do its own thing as long as it conforms to the interface), I need to use pointers. To do that, I need to use new and delete.

No, it is "merely" very common to use new/delete in combination with polymorphism.
You can use object pointers polymorphically whether they've been returned by new or not.

In a lot of board games, the pieces don't really have any state of their own. Instances of the same kind of piece only differ in position, but we don't need to store that in the object itself, because the board keeps track of the positions. So we could just allocate one object for each type of piece on the stack, and then use many pointers to the same objects in the board object.

Quote:

To use new and delete, I'd make a call like
new TilePiece(tpIn)
but that's not gonna work, since it's abstract, but I can't call new on the actual type without knowing what it is.


Someone, somewhere, needs to know the actual type of object to make. You will not get around this. But it does not necessarily have to happen in GameBoard.

Share this post


Link to post
Share on other sites
Quote:
Original post by Barius
Quote:
Original post by Andorien
I see what you mean. Perhaps a better strategy would be to simply derive tiles I want to display graphically from both TilePiece AND Display_Object, while leaving GamePiece as not inheriting from anything?


Possible - but it is really more flexible to not combine both logic and display into a single object.
(BTW, read Bob Martin's articles on OO design principles that are linked to in this post).
In your case - a board game - you could get by with a single integer "pictureNumber" in TilePiece that is used by the renderer as an index to an array of images, while a text mode renderer could use it as an index to a string of ASCII characters.

Quote:

It does other things as well, like displaying the board and all the pieces.


Again, it would be better to put the rendering code somewhere else, in a different object.


I think I see what you mean. Rather than each object being responsible for its own display, each object should rather just contain information about it, and it can be passed to a renderer which would display it as it likes.

Quote:
Quote:

For example:
void GameBoard::InsertTile(const TilePiece& tpIn, const unsigned int& nPosition)
{
TileArray[nPosition] = /* Insert Element Here */
}


Why the comment? You could just write
TileArray[nPosition] = &tpIn;

(I think this is what King Mir meant, he probably forgot the "&" in the parameter list).


You're right, this should work. My only concern is if the object tsIn references goes out of scope.

Quote:
Quote:

Since TilePiece is abstract (and needs to be so each kind of tile can do its own thing as long as it conforms to the interface), I need to use pointers. To do that, I need to use new and delete.

No, it is "merely" very common to use new/delete in combination with polymorphism.
You can use object pointers polymorphically whether they've been returned by new or not.

In a lot of board games, the pieces don't really have any state of their own. Instances of the same kind of piece only differ in position, but we don't need to store that in the object itself, because the board keeps track of the positions. So we could just allocate one object for each type of piece on the stack, and then use many pointers to the same objects in the board object.


So in essence, the board keeps an array of pointer wrappers that also include their current position. I like this, should improve performance.

Quote:
Quote:

To use new and delete, I'd make a call like
new TilePiece(tpIn)
but that's not gonna work, since it's abstract, but I can't call new on the actual type without knowing what it is.


Someone, somewhere, needs to know the actual type of object to make. You will not get around this. But it does not necessarily have to happen in GameBoard.


If I'm wrapping up the pointer + object position in a structure, perhaps when I load up the board initially (or just add elements), I would just have something like this:
TileWrapper twNewTile;
twNewTile.pointer = &SomeTile
twNewTile.Position = SomeNumber;
Board.InsertTile(twNewTile);


BTW, thank you for your help. I see there are problem in my original design, and it's good to get to fixing them.

Share this post


Link to post
Share on other sites
Quote:
My only concern is if the object tsIn references goes out of scope.


That doesn't matter, because you're taking the address of the object that is referenced by the reference, and it should not go out of scope if you've created it in the proper place (e.g., it's not a local object).

Quote:
If I'm wrapping up the pointer + object position in a structure


I think you can deduce the position from the array index, so there's no need to store it explicitly (though maybe that depends on your game).

Share this post


Link to post
Share on other sites
Quote:
Original post by Gage64
Quote:
My only concern is if the object tsIn references goes out of scope.


That doesn't matter, because you're taking the address of the object that is referenced by the reference, and it should not go out of scope if you've created it in the proper place (e.g., it's not a local object).


He has a point, though: it's often convenient to be able to call the function with local objects - or more accurately, it may be the case that he wants the container to "own" the pointed-at objects.

To cover these cases, the "virtual clone idiom" ([google]) is appropriate. You should also look up "polymorphic pImpl".

Quote:
Quote:
If I'm wrapping up the pointer + object position in a structure


I think you can deduce the position from the array index, so there's no need to store it explicitly (though maybe that depends on your game).


Consider that in general, you don't want a pointer specifically, but just some kind of "handle".

Share this post


Link to post
Share on other sites
Quote:
Original post by ZahlmanTo cover these cases, the "virtual clone idiom" ([google]) is appropriate. You should also look up "polymorphic pImpl".


God damn I love you. Looked virtual clone idiom up, and it's exactly the kind of thing I was looking for. It seems stupidly simple, can't believe I didn't think that one up.

Quote:
Quote:
If I'm wrapping up the pointer + object position in a structure


I think you can deduce the position from the array index, so there's no need to store it explicitly (though maybe that depends on your game).


Consider that in general, you don't want a pointer specifically, but just some kind of "handle".[/quote]

As far as the array goes, it's occured to me that, at a later point, I may wish for there to be multiple tile pieces in the same board position. If I use the array index, I'd have to change the design. Of course though, not using the array index to compute position will complicate and slow matters. I'll have to think about it.

Share this post


Link to post
Share on other sites

This topic is 3773 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this