Jump to content

C++ | Can't understand Polymorphism

Hi P
Go to solution Solved by Mira Yurizaki,
On 4/20/2019 at 4:11 PM, Hi P said:

My issue is that I don't understand the point, the purpose, of doing all that when I can simply do something like the following:

 

So both do the exact same thing, but the Polymorphism thing is just a bit longer....

 

Could someone please help me understand this?

 

Thank you very much

What the example is showing, and I think it's a weak example, is polymorphism allows you to cast the class of an object to an ancestor or a "sibling" class.

 

What the example isn't really explaining is all this does is preserve the method signature (that is the method name, return type, and parameters) throughout the parent class and its descendants. The example is fine if attack method of Entity is fixed for all children classes, but if Hero or Goblin implements attack differently through overriding, then you'll have different results if you call attack from an object casted as Entity or one casted as Hero or Goblin. So basically, while this is helpful to teach what polymorphism is, it doesn't teach you where it could be useful.

 

As an example of where it could be useful, in GUI programming, you want something that handles events, like pressing a button or changing which radio button got selected. All of these GUI widgets are classes, like Button, RadioButton, Checkbox, etc. It would be a pain in the butt to create a method signature for every GUI widget type for the same events (e.g., onClick(Button button, event e) and onClick(Checkbox checkbox, event e)). Instead what you can do is make sure all of your GUI classes descend from a common ancestor, then construct a method signature that uses this common ancestor (e.g., onClick(WidgetBase widget, event e)). Since the GUI classes are a descendant of this ancestor, you can cast them to that ancestor and use the same method signature regardless of what GUI widget class you're using.

 

As an example, this is how an event handler is done in C#:

private void KeyButton_Click(object sender, EventArgs e)
{
    Button keyButton = ((Button)sender);
    string keyLetter = keyButton.Name.Substring(0, 1);
    keyLetter = plugBoard.GetRewiredLetter(keyLetter);
    encodeKeyPress(keyLetter);
}

This event handler ties into a series of buttons that represent a keyboard of sorts to process a key press. When one of the buttons is pressed, it calls this function, which casts itself as the generic "object" class as the first argument. Then in the event handler itself, we "uncast" the button object back to the Button class and work with it from there.

 

If polymorphism was not used, the first argument, object sender, would be Button sender instead. Now imagine doing this for every GUI widget you have in this frame work. The benefit to using polymorphism for this, aside from the obvious fewer lines of code you would need, is it's less work if you decide to add or remove a GUI widget. If you added or removed a GUI widget without using this method, you would have to scan through your entire framework to make sure all of the event handler declarations were updated appropriately.

I've read about it, saw examples, watched a couple of videos but I still can't understand its purpose, and it's very hard for me to understand something when I don't understand the purpose at all.

 

I tweaked an example from SoloLearn to make it shorter, I also separated it in spoilers so it's easier to read.

 

Base Class

Spoiler

class Entity
{
protected:
    int attack_points;

public:
    void set_attack(int points)
    {
        attack_points = points;
    }
};

 

 

Inherit Classes (just two)

Spoiler

class Hero : public Entity
{
public:
    void get_attack()
    {
        std::cout << "Damage: " << attack_points << std::endl;
    }
};


class Monster : public Entity
{
public:
    void get_attack()
    {
        std::cout << "Damage: " << attack_points << std::endl;
    }
};

 

 

Polymorphism example from SoloLearn

Spoiler

int main()
{
    Hero warrior;
    Monster goblin;

    Entity *warrior_pointer = &warrior;
    Entity *goblin_pointer = &goblin;

    warrior_pointer->set_attack(25);
    goblin_pointer->set_attack(5);

    warrior.get_attack();
    goblin.get_attack();
    return 0;

    // Output upon running //
    // Damage: 25
    // Damage: 5
}

 

 

My issue is that I don't understand the point, the purpose, of doing all that when I can simply do something like the following:

Spoiler

int main()
{
    Hero warrior;
    Monster goblin;

    warrior.set_attack(25);
    goblin.set_attack(5);

    warrior.get_attack();
    goblin.get_attack();
    return 0;

    // Output upon running //
    // Damage: 25
    // Damage: 5
}

 

 

So both do the exact same thing, but the Polymorphism thing is just a bit longer....

 

Could someone please help me understand this?

 

Thank you very much

Link to comment
Share on other sites

Link to post
Share on other sites

By inheriting from entity, you inherit all the methods and members from entity. Think of a copy and paste. Everything inside the entity class is copy over into your Heroes class so your Hero has everything in Entity plus whatever is inside Hero. 

 

In your case, your Hero has both set_attack and get_attack methods. 

 

It is useful because what if you have monsters, zombies, skeletons and a thousand of other enemies? Do you wish to write the same code for set_attack a thousand times over and over again? 

 

And in your main, what if you have a thousand different types of enemies, are you going to create a thousand different pointers for each unqiue type? Wouldn't it be convenient if you can just use one type, an entity pointer, to store all of them?

Sudo make me a sandwich 

Link to comment
Share on other sites

Link to post
Share on other sites

Polymorphism inheritance, and casting are tightly related.

Imagine that you are writing a bookkeeping program for a business. You may wish to track various kinds of people and how they relate to your business. For example, you could have:

  • Employees
  • External Contractors
  • Customers
  • Clients

But how do you make all these different types of People without constantly redoing all of the work you've already done. Well, one way would be to create a People type. People might have some fields to keep track of their name and other such data. Then, you could derive Employee from People and add fields and data to keep track of payroll and the department they are in. A similar fashion for Contractor, Customer, and Client.

 

But there is a much better use of inheritance that is hidden behind the name Polymorphism: All of the subtypes, Employee, Contractor, Customer and Client are still of the type People. Therefore, anything a People can do, any of the other types can do. This means that you can treat any of the derived types as a People. And that is all that polymorphism is: The ability for a type to appear like it is another type. Inheritance is a common tool to allow polymorphism to take place. Casting is the tool that we use to exploit inheritance: You can down-cast a Customer to a People, and in some languages, up-cast a People to a Customer.

In shorter words: Polymorphism simply means that we can treat some type as if it were another type.

 

Given our business example: Say that we want to be able to calculate our businesses total revenue. We make the assumption that all people associated with our company either owe us money, or we owe them a money. Therefore, we define the People type to have a field that gives us an integer which tells us the amount of money owed. Negative if we owe them, positive if they owe us.

We can go about our tasks normally, building lists of all sorts of Employees and Customers. But that gives us a problem: How do we calculate our revenue when our lists aren't full of People?

Well, the answer is quite simple: Since they are all derived from People, we can simply "trick" our program, using casting (read: Polymorphism), into thinking that our Customers and Employees are just People types, and therefore we can build one method which simply sums calls to People.MoneyOwed.

ENCRYPTION IS NOT A CRIME

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, straight_stewie said:

In shorter words: Polymorphism simply means that we can treat some type as if it were another type.

Thank you so much for the whole explanation on the topic, it cleared things up a bit for me, though I haven't been able to put 1 and 1 together, that's just because I'm slow (sorry), but I hope it will eventually click on my head at some point today! :)

 

Based on the code from my original post, could you explain what's the difference between these two approaches? (it's only the main)

 

Approach #1

Spoiler

int main()
{
    Hero warrior;
    warrior.set_attack(25);
    warrior.get_attack();

    // Output upon running //
    // Damage: 25
}

 

 

Approach #2

Spoiler

int main()
{
    Hero warrior;
    Entity *warrior_pointer = &warrior;
    warrior_pointer->set_attack(25);
    warrior.get_attack();

    // Output upon running //
    // Damage: 25
}

 

 

Approach #2 is basically the example I'm being given upon reading about Polymorphism, and it's mainly what I don't understand.

 

What's the purpose of declaring an Entity (base class) pointer that points to an instance of Hero (derived class from Entity) named Warrior to simply access set_attack()? (Hero method), If any instance from Hero can access that method by itself, as shown in Approach #1.

 

That's what confuses me a lot.

 

What does Approach #2 can do that Approach #1 can't?

 

Thank you! :)

 

Edit: sorry for all the colors, I thought it would help, but it just ended up looking weird

 

Link to comment
Share on other sites

Link to post
Share on other sites

2 hours ago, Hi P said:

Thank you so much for the whole explanation on the topic, it cleared things up a bit for me, though I haven't been able to put 1 and 1 together, that's just because I'm slow (sorry), but I hope it will eventually click on my head at some point today! :)

 

Based on the code from my original post, could you explain what's the difference between these two approaches? (it's only the main)

 

Approach #1

  Hide contents


int main()
{
    Hero warrior;
    warrior.set_attack(25);
    warrior.get_attack();

    // Output upon running //
    // Damage: 25
}

 

 

Approach #2

  Hide contents


int main()
{
    Hero warrior;
    Entity *warrior_pointer = &warrior;
    warrior_pointer->set_attack(25);
    warrior.get_attack();

    // Output upon running //
    // Damage: 25
}

 

 

Approach #2 is basically the example I'm being given upon reading about Polymorphism, and it's mainly what I don't understand.

 

What's the purpose of declaring an Entity (base class) pointer that points to an instance of Hero (derived class from Entity) named Warrior to simply access set_attack()? (Hero method), If any instance from Hero can access that method by itself, as shown in Approach #1.

 

That's what confuses me a lot.

 

What does Approach #2 can do that Approach #1 can't?

 

Thank you! :)

 

Edit: sorry for all the colors, I thought it would help, but it just ended up looking weird

 

The point is the calling code only needs to know about Entity. In a real game, you're probably going to have to keep your entities in some kind of container, such as a std::vector. Are you going to create a Hero vector, a Monster vector, and god knows how many more vectors for each type of entity in your game? Or just one Entity vector (of pointers or reference_wrappers) that can hold all Entities?. 

Link to comment
Share on other sites

Link to post
Share on other sites

24 minutes ago, Unimportant said:

The point is the calling code only needs to know about Entity. In a real game, you're probably going to have to keep your entities in some kind of container, such as a std::vector. Are you going to create a Hero vector, a Monster vector, and god knows how many more vectors for each type of entity in your game? Or just one Entity vector (of pointers or reference_wrappers) that can hold all Entities?. 

Oh I see, SoloLearn didn't explain any of that, it was just a brief code example, nothing related to games, thank you!

Link to comment
Share on other sites

Link to post
Share on other sites

This helped me actually. 

 

So if you wanted a data structure (array, list etc) you can only put of 1 type in there. But if you point to them, then you can put them all there. 

Link to comment
Share on other sites

Link to post
Share on other sites

really the entire point of polymorphism is so you can send the same command to different types and have them behave differently. However in-order for that to work the two objects must share the same base class or even better a common interface (in c++ I think you need to use an abstract class but all that is besides the point)

What you really want to know, I am guessing is WHY and in what situation would you use it in a practical manner, correct?

Putting it very simply:

 

A cat is an animal that can speak.

A dog is an animal that can speak.

 

A cat and dog are both of type animal (base class type) with a behavior of speak (method or function if you like).


Okay so now your client application, let's call it AnimalBeater for the lack of a better idea and only only understands the concept of animal and has no idea what a dog or a cat is. Yet AnimalBeater has a method beatAmimal and when called will make the animal hollar like this:

 

AnimalBeater.beatAmimal(animal typeA) :: typeA.speak();  << "Meow!!!"

AnimalBeater.beatAmimal(animal typeB) :: typeB.speak(); << "Wolf!!!"

I think the most difficult part about this is that you must understand that "typeA" and "typeB" are just parameter names, the important part is how they are declared and that being they are both as type animal. In fact, the class AnimalBeater really only has ONE GENERAL method defined   .beatAmimal(animal type) type.speak() >> cout ::: (you will get meow or wolf).

 

There is no need to define TWO EXPLICIT methods, one for cat and one for dog. Don't do this (well not ever but you understand what i mean):

AnimalBeater.beatAmimal(Cat cat) cat.speak()

AnimalBeater.beatAmimal(Dog dog) dog.speak()

 

I hope this helps.

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

On 4/20/2019 at 9:14 PM, Hi P said:

Thank you so much for the whole explanation on the topic, it cleared things up a bit for me, though I haven't been able to put 1 and 1 together, that's just because I'm slow (sorry), but I hope it will eventually click on my head at some point today! :)

 

Based on the code from my original post, could you explain what's the difference between these two approaches? (it's only the main)

 

Approach #1

  Hide contents


int main()
{
    Hero warrior;
    warrior.set_attack(25);
    warrior.get_attack();

    // Output upon running //
    // Damage: 25
}

 

 

Approach #2

  Hide contents


int main()
{
    Hero warrior;
    Entity *warrior_pointer = &warrior;
    warrior_pointer->set_attack(25);
    warrior.get_attack();

    // Output upon running //
    // Damage: 25
}

 

 

 

What does Approach #2 can do that Approach #1 can't?

 

Thank you! :)

 

Edit: sorry for all the colors, I thought it would help, but it just ended up looking weird

 

The difference is that is that you need a pointer to do polymorphism. 

 

Edit: actually the below is also valid.

Entity entity;

Hero hero;

entity = hero; 

 

E.g. in approach #1

Spoiler

Hero hero;

hero is going to have the apparent type of the class Hero

 

While in approach 2

Spoiler

Entity* heroptr = new Hero(); // this will have the apparent type of Entity instead even though the actual type is Hero. 

 

Entity& hero1 = hero; // likewise this will also have apparent type of the class Entity when actual type is Hero.

 

Entity* hero2 = &hero; // same logic as the above. Apparent type is the base class entity instead of the actual type Hero.

In appoarch two, the variables apparent type started out as apparent type of the base class while in one, the variables are declared using their actual type. 

Sudo make me a sandwich 

Link to comment
Share on other sites

Link to post
Share on other sites

11 minutes ago, wasab said:

Edit: actually the below is also valid.

Entity entity;

Hero hero;

entity = hero; 

Beware, in C++ that slices the Hero object, probably not what you want.

Link to comment
Share on other sites

Link to post
Share on other sites

1 minute ago, Unimportant said:

Beware, in C++ that slices the Hero object, probably not what you want.

What do you mean? Would the hero keep everything inside of a hero? 

Sudo make me a sandwich 

Link to comment
Share on other sites

Link to post
Share on other sites

1 minute ago, wasab said:

What do you mean? Would the hero keep everything inside of a hero? 

Only the Entity part of hero will be copied to entity. All Hero specific fields are lost, "sliced off".

Link to comment
Share on other sites

Link to post
Share on other sites

31 minutes ago, Unimportant said:

Only the Entity part of hero will be copied to entity. All Hero specific fields are lost, "sliced off".

Oh. Didn't know that. 

Sudo make me a sandwich 

Link to comment
Share on other sites

Link to post
Share on other sites

On 4/21/2019 at 4:30 PM, wasab said:

Oh. Didn't know that. 

Yeah, in C++ you have to be concerned with where and how memory is allocated.

 

If it is not a pointer, it is allocated where the container is.  for a local function variable, that means on the stack.  Therefore if you declare a 'Hero', the memory layout of 'Hero' is allocated on the stack.  However, if you declare an 'Entity', the stack will only have the memory layout of an 'Entity', so (including the vtable, which is a table of virtual function pointers) it can only contain at most the members of an entity.

Now, you can probably see how if you are arbitrarily calling virtual methods of 'Entity' that are override by 'Hero', that might cause some major data errors.  Even if we assume 'Entity' is written with proper encapsulation, and therefore cannot be erroneously modified by its subclasses, that doesn't let us off the hook.  In fact, that is only the lesser problem.

 

All member variables are stored as offsets to the beginning of the data structure...so what happens when a 'Hero' member method is called on an 'Entity'...the answer is we have no idea.  It may edit something in the vtable, ie rewriting executable code, or it may rewrite something outside the owning data structure entirely.  We have effectively written to a random memory location.  But it's actually slightly worse, because it's not truly random.  We know it is actually nearby the data structure...and since the data structure isn't dynamically allocated, that is purely relative to its declared location (as opposed to an arbitrary heap allocation and a pointer reference).  That is to say the location we have written to is guaranteed to be nearby or on top of other in-use memory.

So...instead of doing all that, there is a safe option that the compiler can take:  Just make it an 'Entity' and use Entity's copy constructor.

Link to comment
Share on other sites

Link to post
Share on other sites

5 hours ago, Yamoto42 said:

Yeah, in C++ you have to be concerned with where and how memory is allocated.

 

If it is not a pointer, it is allocated where the container is.  for a local function variable, that means on the stack.  Therefore if you declare a 'Hero', the memory layout of 'Hero' is allocated on the stack.  However, if you declare an 'Entity', the stack will only have the memory layout of an 'Entity', so (including the vtable, which is a table of virtual function pointers) it can only contain at most the members of an entity.

Now, you can probably see how if you are arbitrarily calling virtual methods of 'Entity' that are override by 'Hero', that might cause some major data errors.  Even if we assume 'Entity' is written with proper encapsulation, and therefore cannot be erroneously modified by its subclasses, that doesn't let us off the hook.  In fact, that is only the lesser problem.

 

All member variables are stored as offsets to the beginning of the data structure...so what happens when a 'Hero' member method is called on an 'Entity'...the answer is we have no idea.  It may edit something in the vtable, ie rewriting executable code, or it may rewrite something outside the owning data structure entirely.  We have effectively written to a random memory location.  But it's actually slightly worse, because it's not truly random.  We know it is actually nearby the data structure...and since the data structure isn't dynamically allocated, that is purely relative to its declared location (as opposed to an arbitrary heap allocation and a pointer reference).  That is to say the location we have written to is guaranteed to be nearby or on top of other in-use memory.

So...instead of doing all that, there is a safe option that the compiler can take:  Just make it an 'Entity' and use Entity's copy constructor.

Eh. I thought it is because in the case of Entity entity, the computer only allocates enough memory for Entity so anything extra from Hero gets thrown out while a references or pointer doesn't copy anything except the starting address of an object so it doesn't matter. 

Sudo make me a sandwich 

Link to comment
Share on other sites

Link to post
Share on other sites

17 hours ago, wasab said:

Eh. I thought it is because in the case of Entity entity, the computer only allocates enough memory for Entity so anything extra from Hero gets thrown out while a references or pointer doesn't copy anything except the starting address of an object so it doesn't matter.


As far as pointers and references...yes.  A pointer is just an address and a reference is negligibly different from a const pointer.  But again, the original question isn't about a reference or a pointer...it's about an explicitly declared instance.

 

What you're saying doesn't necessarily disagree with what I said.  The question is, in memory, what exactly are an 'Entity' and a 'Hero'.

 

Polymorphism is achieved via what are called vtables.  The spec doesn't exactly say where they have to be, but where they are located actually doesn't matter so much as how they work.

Essentially, a vtable is a table of function pointers.  Lets take the following methods:

virtual void Entity::spam() {
  does something;
}

void void Hero::spam() override {
  eggs();
}

virtual void Hero::eggs() {
  does something else;
}

When you create an Entity, the resulting data structure's vtable will have a single entry:  Entity.spam()

 

The thing is, the vtable for a Hero also only has a single entry:  Hero::eggs().

 

You see, the actual layout of a Hero has 2 vtables:  one for Entity, and one for Hero.  Hero::spam()  is not an addendum, it is a replacement.  It overwrites the Entity::spam() entry in the Hero instance's Entity vtable.

Therefore, a raw memory copy of the Entity portion would leave you with a pointer to Hero::spam(), which when called would (correctly, mind you) receive a 'this' pointer of type Hero...meaning it would think it had a valid Hero vtable and thereby a pointer to Hero::eggs()...


So, the answer is a copy constructor.  They have default implementations that just copy all data members, or you can explicitly declare one.  They take a reference to an object of their own type.  But being a constructor, they also implicitly contain the logic to properly set up the vtable based on the class.  Since the declared variable is an Entity, even if it's being copied form a hero, the Hero constructor won't run and therefore the Entity vtable won't be modified to point to Hero::spam().

Link to comment
Share on other sites

Link to post
Share on other sites

On 4/20/2019 at 4:11 PM, Hi P said:

My issue is that I don't understand the point, the purpose, of doing all that when I can simply do something like the following:

 

So both do the exact same thing, but the Polymorphism thing is just a bit longer....

 

Could someone please help me understand this?

 

Thank you very much

What the example is showing, and I think it's a weak example, is polymorphism allows you to cast the class of an object to an ancestor or a "sibling" class.

 

What the example isn't really explaining is all this does is preserve the method signature (that is the method name, return type, and parameters) throughout the parent class and its descendants. The example is fine if attack method of Entity is fixed for all children classes, but if Hero or Goblin implements attack differently through overriding, then you'll have different results if you call attack from an object casted as Entity or one casted as Hero or Goblin. So basically, while this is helpful to teach what polymorphism is, it doesn't teach you where it could be useful.

 

As an example of where it could be useful, in GUI programming, you want something that handles events, like pressing a button or changing which radio button got selected. All of these GUI widgets are classes, like Button, RadioButton, Checkbox, etc. It would be a pain in the butt to create a method signature for every GUI widget type for the same events (e.g., onClick(Button button, event e) and onClick(Checkbox checkbox, event e)). Instead what you can do is make sure all of your GUI classes descend from a common ancestor, then construct a method signature that uses this common ancestor (e.g., onClick(WidgetBase widget, event e)). Since the GUI classes are a descendant of this ancestor, you can cast them to that ancestor and use the same method signature regardless of what GUI widget class you're using.

 

As an example, this is how an event handler is done in C#:

private void KeyButton_Click(object sender, EventArgs e)
{
    Button keyButton = ((Button)sender);
    string keyLetter = keyButton.Name.Substring(0, 1);
    keyLetter = plugBoard.GetRewiredLetter(keyLetter);
    encodeKeyPress(keyLetter);
}

This event handler ties into a series of buttons that represent a keyboard of sorts to process a key press. When one of the buttons is pressed, it calls this function, which casts itself as the generic "object" class as the first argument. Then in the event handler itself, we "uncast" the button object back to the Button class and work with it from there.

 

If polymorphism was not used, the first argument, object sender, would be Button sender instead. Now imagine doing this for every GUI widget you have in this frame work. The benefit to using polymorphism for this, aside from the obvious fewer lines of code you would need, is it's less work if you decide to add or remove a GUI widget. If you added or removed a GUI widget without using this method, you would have to scan through your entire framework to make sure all of the event handler declarations were updated appropriately.

Edited by Mira Yurizaki
Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, Mira Yurizaki said:

What the example is showing, and I think it's a weak example, is polymorphism allows you to cast the class of an object to an ancestor or a "sibling" class.

Be careful with the terminology, there hasn't been a single cast in this whole thread.

Link to comment
Share on other sites

Link to post
Share on other sites

5 minutes ago, Mira Yurizaki said:

Would you have preferred if I said "substitute?"

I'm sure you're well aware that casting is a explicit type conversion.

In the examples given, no type conversions are made. All they do is refer to a class instance trough a base class pointer.

I'm just a bit worried the OP might go and look up "casting" and get even more confused. I've learned to be careful with the terminology among "students".

Link to comment
Share on other sites

Link to post
Share on other sites

10 minutes ago, Unimportant said:

I'm just a bit worried the OP might go and look up "casting" and get even more confused. I've learned to be careful with the terminology among "students".

If you want to go into exact implementation details of something, sure, we can be pedantic. And yes I will agree that my usage of the word "casting" isn't the best because as you said, no casting of sorts was being done.

 

But let's not get hung up so much with being pedantic here.

 

EDIT: If you're going to point out something wrong, offer a correction as well. I'll be more receptive to that.

Edited by Mira Yurizaki
Link to comment
Share on other sites

Link to post
Share on other sites

16 minutes ago, Mira Yurizaki said:

If you want to go into exact implementation details of something, sure, we can be pedantic. And yes I will agree that my usage of the word "casting" isn't the best because as you said, no casting of sorts was being done.

 

But let's not get hung up so much with being pedantic here.

 

EDIT: If you're going to point out something wrong, offer a correction as well. I'll be more receptive to that.

Not sure why you took this so offensively but no ill intent.

Link to comment
Share on other sites

Link to post
Share on other sites

21 minutes ago, Mira Yurizaki said:

From my point of view, taking your first response as-is paints you as a pedantic asshole.

Forgive me for thinking these were 2 completely different things:

((Entity*)&hero)->set_attack(5);
Entity(hero).set_attack(5);

I know better now. Although my compiler produces 2 completely different results. I guess I should compile without the -pedantic flag.

Link to comment
Share on other sites

Link to post
Share on other sites

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

×