Functionality based on Object Construction

When instantiating a class object, is it possible to add functions to it based on how it's constructed?

For example, say I have a Player class and I create an object player1. In the call to the constructor, I pass a value signifying that the object should be able to do more, such as the ability to use a spell() function, for casting spells, if the player1 object was passed a value indicating that it is a mage. Is that possible?
Last edited on
What do you expect to happen if you call spell() on some arbitrary Player object? How will the compiler know at compile time if it is even valid?
The spell() function is just an example, but it might apply damage to an int representing health. I realise now I may have worded my question poorly. When I said add functions I meant that the functions would already be defined somewhere, but the object would only 'unlock' them based on values passed to the constructor. Would that be possible?
No, the definition of a class is determined at compile time. It can't change at runtime.

You can simulate this behavior though, but you still need to decide for yourself what should happen if you try to invoke a behavior that isn't supported.

However, a bigger thing to consider is why you would ever even need to do this in the first place - could you give an example use case?
Sorry for not replying sooner. I was tired and had to go to bed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>

using namespace std;

class Player
{
    public:
        enum PlayerClass
        {
            Warrior,
            Mage
            // etc
        };

        Player(PlayerClass type)
        {
            if(type == Warrior)
            {
                // Set members based on PlayerClass type.
                health = 100;
                attack = 50;
                defense = 60;
                intelligence = 5;
                speed = 30;

                /* 
                    Enable access to extra (Warrior specific) functions.
                    Possibly instantiate new class object (Warrior warrior).
                */
            }
            else if(type == Mage)
            {
                // Set members based on PlayerClass type.
                health = 60;
                attack = 20;
                defense = 25;
                intelligence = 80;
                speed = 70;

                /* 
                    Enable access to extra (Mage specific) functions.
                    Possibly instantiate new class object (Mage mage).
                */
            }
            // etc
        }

    private:
        int health;
        int attack;
        int defense;
        int intelligence;
        int speed;
};

int main()
{
    Player player1(Player::Mage); // Instantiate player1 as PlayerClass type 'Mage'

    return 0;
}

In this example, you can see that based on the enum passed to the constructor, certain stats are given to the object. It is here that I would like to enable extra functions for the object. One way I thought it might work would be to instantiate an object of another class representing a PlayerClass type, such as Warrior or Mage, with the extra functions defined inside there. However, that probably wouldn't work, since like you said, the class has to be defined at compile time.

In a class such as this I would normally derive Player from a Character class and place the 'stats' inside there, so that an Enemy class would have access to them as well. For this example though I don't think it matters.

I suppose the simplest way to vary functionality would be to create new classes representing PlayerClasses, such as (where have I heard this before) Warrior or Mage and derive them from Player or directly from Character, depending on the class hierarchy. I just thought it would be cool to create one kind of object that would vary in functionality depending on what parameters you gave it. If it's not possible then I'll go with the simple approach, but I'd like to know for sure.

Before I can know for sure though, I have to ask you, how can you simulate the desired behaviour? You say that it can be done, but that it's not supported. What do you mean by that?

Anyway, I hope this explanation makes things clearer for you. Your advice is much appreciated.
This is where you'll probably want to use polymorphism and make Mage and Warrior inherit from Player. Then give Player standard functions like Action1 and Action2 (I'm assuming Mage and Warrior will have the same number of functions), then overrider these by making them virtual in Player and redefine them as class specific actions in Mage and Warrior.
Think about the interface. How will the Player objects be used in the program?
@shadowmouse
I haven't learned polymorphism yet, so I'm probably missing some perspective here, but wouldn't it be easier to just define all the relevant functions in the classes they'll be used in and make the parent class constructors protected to stop you from instantiating objects of those classes? Also, the classes wouldn't necessarily have the same number of functions. I don't know if that makes a difference to the effectiveness of polymorphism in this case.

@keskiverto
I suppose one thing I'd do is make a battle() function belonging to either Character or Player and have it accept a Player (or one of its derived classes) object and an Enemy object and apply a series of battle related functions inside, such as battleMenu(), compareSpeed(), checkHealth() and so on. That's only a rough idea though. For the moment I just want to look into ways in which Player related objects can and should be instantiated.
What polymorphism is (basically) is the ability for a pointer to a base class, to point to a derived class. Say you had the following
1
2
3
4
5
6
7
class Player{
private:
//stats
public:
    virtual void MeleeAttack() = 0;
    virtual void RangeAttack() = 0;
};

This is an abstract base class as the functions have been declared pure virtual (=0;). You could then create
1
2
3
4
5
6
7
8
class Mage: public Player {
private:
//Inherits stats from Player
public:
    Mage(); //Constructor can be defined to set up Mage stats
    void MeleeAttack(); //Can be defined to do a magical melee attack
    void RangeAttack(); //Can be defined to do a magical range attack
};

You could then also create
1
2
3
4
5
6
7
8
class Warrior: public Player {
private:
//Inherits stats from Player
public:
    Warrior(); //Constructor can be defined to set up Warrior stats
    void MeleeAttack(); //Can be defined to do a physical melee attack
    void RangeAttack(); //Can be defined to do a physicalrange attack
};

You could then do this:
1
2
std::unique_ptr<Player> player = std::make_unique<Mage>(/*Mage constructor parameters*/);
player->MeleeAttack(); //Player will do a magical melee attack 

or you could do this:
1
2
std::unique_ptr<Player> player = std::make_unique<Warrior>(/*Warrior constructor parameters*/);
player->MeleeAttack(); //Player will do a physical melee attack 
> I just thought it would be cool to create one kind of object that would vary in functionality
> depending on what parameters you gave it. If it's not possible then I'll go with the simple
> approach, but I'd like to know for sure.

>> I haven't learned polymorphism yet

This would be a simple way, (using only things that you have learnt so far),
to simulate types specified/changed at run time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <string>

struct player
{
    enum player_type { MAGE, NOT_MAGE };

    player_type type ;
    std::string name;

    player( player_type type, std::string name ) : type(type), name(name) {}

    void transform( player_type new_type ) { type = new_type ; }

    void cast_spell() const
    {
        if( type == MAGE ) std::cout << name << " could cast a spell\n" ;
        // else do nothing (or perhaps throw something)
    }
};

int main()
{
    player billie( player::MAGE, "Lady Day" ) ;
    billie.cast_spell() ; // Lady Day could cast a spell

    player other( player::NOT_MAGE, "xxx" ) ;
    other.cast_spell() ; // do nothing

    other.transform( player::MAGE ) ;
    other.cast_spell() ; // xxx could cast a spell
}

http://coliru.stacked-crooked.com/a/7505489f450b9761
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Mage {
  void spell();
};

struct Warrior{
  void shoot();
};

int main() {
  vector<Mage> foos(42);
  vector<Warrior> bars(7);
  for ( auto & x : foos ) {
    x.spell();
  }
  for ( auto & x : bars ) {
    x.shoot();
  }
}

Different types with different interface are kept and handled separately.

The foos and bars are two objects that share some interface from the vector template, but still differ in type and interface. One got spell() to its elements and the other got shoot().

The struct player, however, is one type with one interface. Polymorphism via inheritance and virtual functions is just different way to do what the player::type is used for.


There is yet another way to compose objects with same type and interface:
function pointers. Store a list of those pointers in your object. Then you do have different challenge though: how to map, i.e. check and reach specific functionality.

For example, a list of strings to hold names of spells and corresponding list of function pointers. Show names to user. Get index of chosen spell. Use index to call function from the other list. Parameter and return types must be same for all functions in the list though.
Sorry for the lengthy absence. I have been trying to learn polymorphism the past few days. I'm not finding it easy either. It's not an easy concept to get your head around.

@shadowmouse
Thanks for trying to explain polymorphism to me. Your example is what prompted me to go and try to learn it myself. Still trying to grasp it at the moment.

@JLBorges
Aside from a small gripe, being the fact that a user of the class may call functions that have no effect, but can still be called, that is pretty much exactly what I was looking for. Thank you very much for that cool solution.

Thanks everyone for all the help. Again, sorry for the delay in getting back to you.
Have you ever written an overloaded operator<< for output?
It should take and return a reference to std::ostream.

However, do we ever output to ostream objects? No and yes. We usually output to cout, ofstream or ostringstream. They all inherit ostream and can be used like ostream, but do things differently. The operator<< does not need to care.

That is one example of runtime polymorphism in action.
Last edited on
I haven't studied operator overloading or files as of yet, so your example is a little hard to follow.

However, I think I understand what you're saying in that polymorphism is the changing of an objects type at run-time.
I would not say "change type of object". C++ uses static, strong typing. An object is what an object is.

The runtime polymorphism is more about objects having apparent type (common interface) and true type (implementation, behaviour).

Lets do a simplified example with output:
1
2
3
void push( std::ostream & out ) {
  out << 42;
}

The only thing that the push() requires is that there is operator<< that shovels 42 into ostream. The push() can be called on anything that apparently is an ostream.
1
2
3
4
5
6
7
8
using std::cout;
std::ostringstream foo;
std::ofstream bar( "bar.txt" );

// use push
push( cout );
push( bar );
push( foo );

The cout, foo and bar are three objects. They have different types. The type is set on creation, during compilation. They all inherit from ostream, so they fulfil the static requirements of the push(). What actually happens on shoveling the 42, is different.

Too static?
1
2
3
4
5
6
7
8
9
int main( int argc, char ** argv ) {
  if ( 1 < argc ) {
    std::ofstream off( argv[1] );
    push( off );
  } else {
    push( std::cout );
  }
  return 0;
}

The compiler can verify that both branches have correct typing, but it cannot know which true object types are actually created/used during each run of the program.

We could create a new type that inherits (directly or indirectly) from ostream and call the push() with object of that type. The result of "out << 42;" could again be different.


There is a way to check for "true" type and thus have access to additional members that the interface of the base class does not have:
1
2
3
4
5
6
7
void foo( Player * p ) {
  auto m = dynamic_cast<Mage*>(p);
  if ( m ) {
    m->spell();
  }
  p->attack();
}

If the true type of object pointed to by p is Mage (or derived from Mage), then m is a valid pointer. Otherwise m has value nullptr. The Mage has to derive from Player, obviously.
Also you can use dynamic_cast with reference types.
It has slightly different behavior: if type you are casting to is not actual object type, then it emits exception.

Generally downcasting classes is sign of problems in program desing. You can, for example, make another class "action" which has a name, and own implementation of said action. Have a container of actions in Player class. So you can enumerate all possible actions, select one you need and even add/remove actions at runtime.

Or you can just have again container of possible actions, but make actual actions virtual functions in base class. This way you can querry which actions you can use before you do so (and if you ever will need to create new casting class not inherited from mage, base game logic ill still work)
Topic archived. No new replies allowed.