A polymorphism question

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

User avatar
Khearts
ES Beta Backer
ES Beta Backer
Posts: 50
Joined: Sun Oct 10, 2010 5:07 pm
Current Project: Super Mario Bros Clone and 3D Engine
Favorite Gaming Platforms: Dreamcast
Programming Language of Choice: C/++

A polymorphism question

Post by Khearts »

Say for example I have

class Ship

class Player : public Ship

class EShip : public Ship

and I have

vector <Ship*> vShip;

This is probably a very simple answer, but I want to be able to add both of the derived classes to the vector and access data only unique to one of the derived classes.

If I include a data member int score into the Player class, that means (under the framework of my code) I have to add those data in the parent class Ship. This will thus be added in EShip, which I do not want. Is there any way around this?
User avatar
TheBuzzSaw
Chaos Rift Junior
Chaos Rift Junior
Posts: 310
Joined: Wed Dec 02, 2009 3:55 pm
Current Project: Paroxysm
Favorite Gaming Platforms: PC
Programming Language of Choice: C++
Contact:

Re: A polymorphism question

Post by TheBuzzSaw »

Well, that begs a few questions. First off, how do you know that you are specifically pulling a Player out of a bag full of Ships? At what point are you wanting to know about a Player's score? Can you have a separate vector somewhere that contains only Players? (With that, you would probably use the vector of Ships for rendering and the vector of Players just for scoring purposes.)
User avatar
Khearts
ES Beta Backer
ES Beta Backer
Posts: 50
Joined: Sun Oct 10, 2010 5:07 pm
Current Project: Super Mario Bros Clone and 3D Engine
Favorite Gaming Platforms: Dreamcast
Programming Language of Choice: C/++

Re: A polymorphism question

Post by Khearts »

Right, that's what I was wondering, also. I'm getting the player's score throughout the entire game. I guess I just wanted to be able to add functionality to one derived class without making the base class and its other derived classes include those data.
JesseGuarascia
Chaos Rift Cool Newbie
Chaos Rift Cool Newbie
Posts: 70
Joined: Mon Dec 13, 2010 10:55 pm

Re: A polymorphism question

Post by JesseGuarascia »

I think, in all technicality, something you could do would be:

Code: Select all

std::vector<Ship*> ships;

ships.push_back(new Player());

Player player = (Player)(*ships.at(0));
The only problem with that, is that you have to have a way of knowing that ships.at(0) is a Player. I think that'll work, idk :/
-- Jesse Guarascia

I like C/++, SDL, SFML, OpenGL and Lua. If you don't like those, then gtfo my sig pl0x (jk trollololololol)
User avatar
GroundUpEngine
Chaos Rift Devotee
Chaos Rift Devotee
Posts: 835
Joined: Sun Nov 08, 2009 2:01 pm
Current Project: mixture
Favorite Gaming Platforms: PC
Programming Language of Choice: C++
Location: UK

Re: A polymorphism question

Post by GroundUpEngine »

JesseGuarascia wrote:I think, in all technicality, something you could do would be:

Code: Select all

std::vector<Ship*> ships;

ships.push_back(new Player());

Player player = (Player)(*ships.at(0));
The only problem with that, is that you have to have a way of knowing that ships.at(0) is a Player. I think that'll work, idk :/
Adding to the OP, you can identify a specific ship and use a std::map ;)

Code: Select all

std::map<string, Ship*> ships;

Ship temp = new Player();
ships["bill"] = temp;

Player player = (Player)*ships["bill"];
JesseGuarascia
Chaos Rift Cool Newbie
Chaos Rift Cool Newbie
Posts: 70
Joined: Mon Dec 13, 2010 10:55 pm

Re: A polymorphism question

Post by JesseGuarascia »

GroundUpEngine wrote:
JesseGuarascia wrote:I think, in all technicality, something you could do would be:

Code: Select all

std::vector<Ship*> ships;

ships.push_back(new Player());

Player player = (Player)(*ships.at(0));
The only problem with that, is that you have to have a way of knowing that ships.at(0) is a Player. I think that'll work, idk :/
Adding to the OP, you can identify a specific ship and use a std::map ;)

Code: Select all

std::map<string, Ship*> ships;

Ship temp = new Player();
ships["bill"] = temp;

Player player = (Player)*ships["bill"];
That. I was gonna suggest a map, but I was trying to keep it as a simplex vector. Definitely use a map though if you're comfortable.
-- Jesse Guarascia

I like C/++, SDL, SFML, OpenGL and Lua. If you don't like those, then gtfo my sig pl0x (jk trollololololol)
User avatar
GroundUpEngine
Chaos Rift Devotee
Chaos Rift Devotee
Posts: 835
Joined: Sun Nov 08, 2009 2:01 pm
Current Project: mixture
Favorite Gaming Platforms: PC
Programming Language of Choice: C++
Location: UK

Re: A polymorphism question

Post by GroundUpEngine »

JesseGuarascia wrote: That. I was gonna suggest a map, but I was trying to keep it as a simplex vector. Definitely use a map though if you're comfortable.
Oh kk, agreed simple is always better.
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
Posts: 10294
Joined: Thu May 20, 2004 2:04 pm
Current Project: Elysian Shadows
Favorite Gaming Platforms: Dreamcast, SNES, NES
Programming Language of Choice: C/++
Location: Studio Vorbis, AL
Contact:

Re: A polymorphism question

Post by Falco Girgis »

JesseGuarascia wrote:I think, in all technicality, something you could do would be:

Code: Select all

Player player = (Player)(*ships.at(0));
Kind of. The idea you are trying to present would work (the code you've presented won't), but it's still neglecting the main question (making it irrelevant): how do you keep track of the ship's type data to know how to make the cast in the first place?

JesseGuarascia wrote:

Code: Select all

Player player = (Player)(*ships.at(0));
First of all, you've already dereferenced the ship as a SHIP (a less specific type), so you can't cast it to a more specific type after the dereference.

It would be:
JesseGuarascia wrote:

Code: Select all

Player player = *((Player *)ships.at(0));
which would still be incorrect. You are dereferencing the pointer then copy constructing it over to player (which is on the stack). Even if you didn't mind not accessing the original, you would be incurring a performance penalty from the copy. The correct way would be to use either a reference or a pointer:

Code: Select all

Player *player = (Player *)ships[0];
player->somePlayerSpecificVariable = 100;
But even now we haven't addressed the issue at hand. Cool, we can now refer to a ship as a player. But how do we know which ships really ARE players? If you dereference a ship that has not been allocated as a player, your application is going to segfault.

You are going to need some sort of metadata containing the "actual" type that the object was instantiated as. In a language like C# or Java, this metadata is built-in, and you could safely cast it with the call of a single function. With C++, this is more complicated. You can either 1) rethink your design 2) enable RTTI or 3) create your own metadata.

It's really up to you, although I would never recommend option #2 in a statically compiled language. There is quite a bit of overhead associated with RTTI (runtime type identification), which is why it is disabled in C++ by default. If you absolutely require this kind of functionality, you should be using a higher-level language to begin with, rather than butchering the shit out of C++.

1) Rethink your design.
Is there any particular reason you MUST store all of your ships in a single vector/array/structure other than the fact that it looks cute? Can you not have a structure for each specific type of ship (and maybe even keep the "ship" vector/array for things operating on the generic type)? The chances are that since this problem arose without using RTTI in the first place, you can very well use separate structures to solve it.

2) Depending on what compiler you use, pass the -frtti flag (thus enabling RTTI and all of its terrible associated overhead).

3) Create your own metadata.
In many scenarios (specifically in polymorphic languages not supporting RTTI), it is often necessary to create your own metadata for class types. This data can essentially be an enumeration representing the specific type of ship each object is. This data allows you to SAFELY cast down to a subclass:

Code: Select all

class Ship {
public: 
    enum TYPE { PLAYER, ESHIP };
private:
    TYPE _type;
public:
    Ship(Type type): _type(type) {}  
    TYPE getType() const { return _type; }  
};
Now each subclass implicitly registers its own type upon its construction:

Code: Select all

class Player: public Ship {
public:
    Player(): Ship(Ship::PLAYER) {}
};
and

Code: Select all

class EShip: public Ship {
public:
    EShip(): Ship(Ship::ESHIP) {}
};
Now when you are iterating through the generic storage device, you are able to safely check an object's type:

Code: Select all

for(unsigned int  i = 0; i < ship.size(); ++i) {
    switch(ship[i]->getType()) {
        case Ship::PLAYER:
            ((Player*)ship[i])->somePlayerOnlyVariable = 10;
            break;
        case Ship::ESHIP:
            ((EShip*)ship[i])->someEShipOnlyVariable = 20;
            break;
        default: //you done fucked something up
    }
}
The only downside is that the parent class gains a direct/semi-direct dependency on its descendent classes, as the enumerated type must be declared within the parent. This means that adding a new sub class to the Ship class requires modifying the enumeration in the parent class to accomodate. The only time that this REALLY matters is when you have client code creating subclasses in either another library or compilation unit (where you literally cannot modify the parent source).

While many OO-extremist whores turn up their nose at such a practice (for the dependency reason), I believe this is the way to go when you don't have external compilation units creating client subclasses. These same OO programmers that bitch about such practices are of a mentality generally created from working solely with higher-level JIT compiled languages such as C# and Java--where RTTI is taken for granted and the compiler babies them. In a statically compiled language you must think outside of the box to solve problems such as this (or be a bitch, enable RTTI, and basically destroy the point of using C/++ to begin with).

There are other, FAR more advanced methods of implementing the above system that WILL enable client code to dynamically register additional types (that we use in ES), but I'm sure it would be overkill for your application.
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
Posts: 10294
Joined: Thu May 20, 2004 2:04 pm
Current Project: Elysian Shadows
Favorite Gaming Platforms: Dreamcast, SNES, NES
Programming Language of Choice: C/++
Location: Studio Vorbis, AL
Contact:

Re: A polymorphism question

Post by Falco Girgis »

GroundUpEngine wrote:

Code: Select all

Player player = (Player)*ships["bill"];
You guys cannot cast dereferenced objects like that. The cast has to be performed before the dereference:

Code: Select all

Player player = *((Player *)ships["bill"]);
And even then, don't forget you are COPY CONSTRUCTING the object. You should never do that, even if you don't want to be able to edit the original. Use a constant pointer or reference as such:

Code: Select all

const Player *player = (const Player *)ships["bill"];
Vector2 pos = player->getPosition();
Or (if you prefer not to use pointers):

Code: Select all

const Player &player = *((const Player *)ships["bill"]);
Vector2 pos = player.getPosition()
The two are identical.
User avatar
GroundUpEngine
Chaos Rift Devotee
Chaos Rift Devotee
Posts: 835
Joined: Sun Nov 08, 2009 2:01 pm
Current Project: mixture
Favorite Gaming Platforms: PC
Programming Language of Choice: C++
Location: UK

Re: A polymorphism question

Post by GroundUpEngine »

To OP, my bad I wouldn't normally do that :P
GyroVorbis wrote:The only downside is that the parent class gains a direct/semi-direct dependency on its descendent classes, as the enumerated type must be declared within the parent.
Also, would this solve that?

Code: Select all

class Ship {
public:
    virtual string getType()
    {
        return "Ship";
    }
};
class Shippy : public Ship {
public:
    string getType()
    {
        return "Shippy";
    }
};
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
Posts: 10294
Joined: Thu May 20, 2004 2:04 pm
Current Project: Elysian Shadows
Favorite Gaming Platforms: Dreamcast, SNES, NES
Programming Language of Choice: C/++
Location: Studio Vorbis, AL
Contact:

Re: A polymorphism question

Post by Falco Girgis »

GroundUpEngine wrote:To OP, my bad I wouldn't normally do that :P
GyroVorbis wrote:The only downside is that the parent class gains a direct/semi-direct dependency on its descendent classes, as the enumerated type must be declared within the parent.
Also, would this solve that?

Code: Select all

class Ship {
public:
    virtual string type()
    {
        return "Ship";
    }
};
class Shippy : public Ship {
public:
    string type()
    {
        return "Shippy";
    }
};
Sure thing. :D

You get into some bullshit though when you must INSTANTIATE instances of the client classes within the host code. Consider a robust component system where client code can literally create new components by subclassing "Component." Now when an entity is deserialzed, the precompiled Entity code within the engine must INSTANTIATE the objects of the child component subclass. Keep in mind that in order for your virtual function to override the base "type()" function (or for polymorphism to even work), the object must be INSTANTIATED (allocated) at its most specific level--then it can be polymorphed to whatever later. The solution to this problem requires some form of dynamic type registry where the client code registers "allocators" for each specific class type that physically "allocates" the object at its most specific child type then polymorphs it back to its parent type before handing it off to the host code.

Personally, I prefer the enumeration in instances where a client will never be subclassing, because 1) it's type-safe 2) it's internally represented as an integer, so it's far smaller than a string 3) comparing an integer is like one assembly instruction while comparing a character array is a far slower call to strcmp().
User avatar
GroundUpEngine
Chaos Rift Devotee
Chaos Rift Devotee
Posts: 835
Joined: Sun Nov 08, 2009 2:01 pm
Current Project: mixture
Favorite Gaming Platforms: PC
Programming Language of Choice: C++
Location: UK

Re: A polymorphism question

Post by GroundUpEngine »

GyroVorbis wrote:Sure thing. :D

You get into some bullshit though when you must INSTANTIATE instances of the client classes within the host code. Consider a robust component system where client code can literally create new components by subclassing "Component." Now when an entity is deserialzed, the precompiled Entity code within the engine must INSTANTIATE the objects of the child component subclass. Keep in mind that in order for your virtual function to override the base "type()" function (or for polymorphism to even work), the object must be INSTANTIATED (allocated) at its most specific level--then it can be polymorphed to whatever later. The solution to this problem requires some form of dynamic type registry where the client code registers "allocators" for each specific class type that physically "allocates" the object at its most specific child type then polymorph it back to its parent type before handing it off to the host code.

Personally, I prefer the enumeration in instances where a client will never be subclassing, because 1) it's type, safe 2) it's internally represented as an integer, so it's far smaller than a string 3) comparing an integer is like one assembly instruction while comparing a character array is a far slower call to strcmp().
Great points man! I think I prefer the enum myself now, haha ;)
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
Posts: 10294
Joined: Thu May 20, 2004 2:04 pm
Current Project: Elysian Shadows
Favorite Gaming Platforms: Dreamcast, SNES, NES
Programming Language of Choice: C/++
Location: Studio Vorbis, AL
Contact:

Re: A polymorphism question

Post by Falco Girgis »

GroundUpEngine wrote:
GyroVorbis wrote:Sure thing. :D

You get into some bullshit though when you must INSTANTIATE instances of the client classes within the host code. Consider a robust component system where client code can literally create new components by subclassing "Component." Now when an entity is deserialzed, the precompiled Entity code within the engine must INSTANTIATE the objects of the child component subclass. Keep in mind that in order for your virtual function to override the base "type()" function (or for polymorphism to even work), the object must be INSTANTIATED (allocated) at its most specific level--then it can be polymorphed to whatever later. The solution to this problem requires some form of dynamic type registry where the client code registers "allocators" for each specific class type that physically "allocates" the object at its most specific child type then polymorph it back to its parent type before handing it off to the host code.

Personally, I prefer the enumeration in instances where a client will never be subclassing, because 1) it's type, safe 2) it's internally represented as an integer, so it's far smaller than a string 3) comparing an integer is like one assembly instruction while comparing a character array is a far slower call to strcmp().
Great points man! I think I prefer the enum myself now, haha ;)
Y'know... actually if you COMBINED the two methods, having a virtual/purely virtual getType() (implemented by the child) that returns an enumerated Type would be the best solution.

The one I presented requires EVERY instance of the parent to store its own type. With your method, you only need the storage of ONE enumeration instance per class. That's actually going to be your best bet...
User avatar
GroundUpEngine
Chaos Rift Devotee
Chaos Rift Devotee
Posts: 835
Joined: Sun Nov 08, 2009 2:01 pm
Current Project: mixture
Favorite Gaming Platforms: PC
Programming Language of Choice: C++
Location: UK

Re: A polymorphism question

Post by GroundUpEngine »

GyroVorbis wrote:
GroundUpEngine wrote:
GyroVorbis wrote:Sure thing. :D

You get into some bullshit though when you must INSTANTIATE instances of the client classes within the host code. Consider a robust component system where client code can literally create new components by subclassing "Component." Now when an entity is deserialzed, the precompiled Entity code within the engine must INSTANTIATE the objects of the child component subclass. Keep in mind that in order for your virtual function to override the base "type()" function (or for polymorphism to even work), the object must be INSTANTIATED (allocated) at its most specific level--then it can be polymorphed to whatever later. The solution to this problem requires some form of dynamic type registry where the client code registers "allocators" for each specific class type that physically "allocates" the object at its most specific child type then polymorph it back to its parent type before handing it off to the host code.

Personally, I prefer the enumeration in instances where a client will never be subclassing, because 1) it's type, safe 2) it's internally represented as an integer, so it's far smaller than a string 3) comparing an integer is like one assembly instruction while comparing a character array is a far slower call to strcmp().
Great points man! I think I prefer the enum myself now, haha ;)
Y'know... actually if you COMBINED the two methods, having a virtual/purely virtual getType() (implemented by the child) that returns an enumerated Type would be the best solution.

The one I presented requires EVERY instance of the parent to store its own type. With your method, you only need the storage of ONE enumeration instance per class. That's actually going to be your best bet...
Interesting, that mixes quite well! :cheers:
User avatar
THe Floating Brain
Chaos Rift Junior
Chaos Rift Junior
Posts: 284
Joined: Tue Dec 28, 2010 7:22 pm
Current Project: RTS possible Third Person shooter engine.
Favorite Gaming Platforms: PC, Wii, Xbox 360, GAME CUBE!!!!!!!!!!!!!!!!!!!!!!
Programming Language of Choice: C/C++, Python 3, C#
Location: U.S

Re: A polymorphism question

Post by THe Floating Brain »

Im a little slow on the uptake on this buuuuut...

Code: Select all

class Ship
{
     protected:
     int Health; //For ex.
      public:
      float move(float gotoX, float GotoY); //For example demonstarting what you could have for more abstract methods.
};

class ShipB : public Ship
{
   protected:
      int Score; //This could have more things that would be more for the player.
};
class Player : public ShipB
class EShip : public Ship
or

Code: Select all

class AbstractData
//Take the following 2 classes from the code above.//
class Ship //Creates a instance of the abstract data class.//
class ShipB : public Ship
class Player : public ShipB
class EShip : public Ship
std::vector<*AbstractData> MyVect;
{
  //Just a random instanchyation not saying this is a good way to do this.//
  *AbstractData A;
  MyVect.push_back(A);
}
//Now you can use this std::vector to point to the data in either class.//
Idk if that's the best solution.
Last edited by THe Floating Brain on Wed Mar 09, 2011 6:19 pm, edited 1 time in total.
"Why did we say we were going to say we were going to change the world tomorrow yesterday? Maybe you can." - Myself

ImageImage
Post Reply