Accessing Child Members From Array of Parent

I am currently in development of a little game project but I've run into a pretty significant issue. I have an array of class 'Entity' which is supposed to represent a map that holds only entities. I have an Entity class with a 'name' variable so far. I also have a 'Player' class that inherits from 'Entity' but the Player class also has a health variable. I can insert the player into the level by setting a level slot equal to the player object (because player is an entity). However, I cannot extract the player's health (using a player getHealth() function) because the level pointer is of type 'Entity'.

I tried looking up type conversions but I couldn't get the principle down. Is there a better way to do this whole process? My goal is to have many inherited classes from Entity that can all exist in the level, but have different members depending on their types.
Something along these lines, perhaps:

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
#include <iostream>
#include <vector>
#include <memory>

struct entity
{
   explicit entity( std::string name ) : name(name) {}

   virtual ~entity() = default ;
   // etc.

   std::string name ;
};

// all classes that have health implement this interface 
struct has_health
{
    protected:
        explicit has_health( int health ) : health_index(health) {}
        int health_index = 0 ;

    public:
        virtual ~has_health() = default ;
        // etc.

        virtual int health() const noexcept { return health_index ; }
};

struct player : entity, has_health
{
    player( std::string name, int health ) : entity{name}, has_health{health} {}
};

int main()
{
    std::vector< std::unique_ptr<entity> > entities ;

    entities.push_back( std::make_unique<entity>( "entity one" ) ) ;
    entities.push_back( std::make_unique<player>( "player two", 25 ) ) ;
    entities.push_back(nullptr) ;
    entities.push_back( std::make_unique<entity>( "entity three" ) ) ;
    entities.push_back( std::make_unique<player>( "player four", 70 ) ) ;

    for( const auto& up : entities )
    {
        if(up)
        {
            std::cout << up->name << " : " ;

            // check if his entity implements has_health
            const has_health* phh = dynamic_cast< const has_health* >( up.get() ) ;

            if(phh) std::cout << "health == " << phh->health() << '\n' ;

            else std::cout << "does not have health\n" ;
        }
        else std::cout << "nullptr\n" ;
    }
}

http://coliru.stacked-crooked.com/a/9a78dc0a9c076f59
My goal is to have many inherited classes from Entity that can all exist in the level, but have different members depending on their types.

That's a fine idea. It lets you process all the entities in the level and I expect you have good reasons to do that.

One way to handle this is to have class Entity store an entityType that tells which type of entity it really is. Then you can check if the Entity is a Player and, if so, cast Entity to Player and access the health.

But you might also find that it makes more sense to store the Player(s) on a level in a separate container so you access the players directly as needed. Of course, that makes it harder to process all items on the level. It's a trade off.

One technique is to create a function that will call a callback function on every item in the level.
1
2
typedef bool ForEachEntityCallback(const Entity &ent, void *params);
bool forEachEnt(ForEachEntCallback func, void *params=0);


forEachEnt() calls func(ent, params) on each ent.

This way you can store the entities in any way that makes sense. If you add a new entity somewhere, you just have to change forEachEnt() and poof! All the code that processes all the entities will work.
@JLBorges
Thank you for the fast response! Oh boy, I hope I am not way in over my head here but after reading the code, I feel like I can only understand a little bit of it. I think I have an idea of what you are saying but out of curiosity, why did you choose to use structs? If that makes a big difference that as.

@dhayden
Your first suggestion about checking the entityType was something I tried to do at first. I couldn't quite get the hang of casting it to another type though. I'll have to keep practicing. Your second suggestion sounds very efficient, but I may need to practice a bit more to understand it. I'm not a complete beginner, but sometimes struggle with OOP having not come from it in my experience.
> why did you choose to use structs? If that makes a big difference that as.

It does not make any difference.
"The keywords (class and struct) are identical
(except for the default member access and the default base class access.)"
https://en.cppreference.com/w/cpp/language/class


> I feel like I can only understand a little bit of it.

There are two aspects in the code with which you may not be familiar:

a. smart pointer
std::unique_ptr: https://www.drdobbs.com/cpp/c11-uniqueptr/240002708
std::make_unique https://docs.microsoft.com/en-us/cpp/standard-library/memory-functions?view=vs-2019#make_unique

b. dynamic_cast
https://en.cppreference.com/w/cpp/language/dynamic_cast


The idea of having a separate class has_health is from a C++ idiom called mix-in: has_health is a "mix-in class"

The model that is used here is the first model mentioned in this article:
"A mixin models some small functionality slice and is not intended for standalone use. Rather, it is supposed to be composed with some other class needing this functionality

One use of mixins in object-oriented languages involves classes and multiple inheritance. In this model, a mixin is represented as a class, which is then referred to as a "mixin class," and we derive a composed class from a number of mixin classes using multiple inheritance."
https://www.drdobbs.com/cpp/mixin-based-programming-in-c/184404445
(Though very often, mix-ins are implemented in C++ using the second model and CRTP)

Mix-ins are commonly used in C++ as a more flexible alternative to C-style type fields and callbacks.
Topic archived. No new replies allowed.