CiaranWelsh
CiaranWelsh

Reputation: 7691

How can I achieve polymorphism in this simple minimal example?

The problem

I'm trying to achieve polymorphism by having types of Event subtypes inherit from the base class Event. The idea is that there is always an active event, denoted by the pointer in Runner (Event* current_event). When the Runner::step method is called, the Event::go method is called, which flips some switches and changes the current behaviour of the program. Then, the pointer is pointed at a different event until Runner::step is called again.

The issue is that the pointer is always using the base class Event versions of variables. It might be a simple fix but I'd be greatful if somebody could point it out to me.

The code


#include <iostream>

using namespace std;


struct Flags {
public:
    bool withCheese = false;
    bool withBiscuits = false;

};

class Event {
    std::string description = "The base class";
public:
    std::string id = "Base";

    Event() = default;

    ~Event() = default;

    Event(Event const &event) = default;

    Event &operator=(Event &&) = default;

    virtual void go(Flags &flags) {};

    virtual void printDescription() {
        std::cout << description << std::endl;
    }
};


class EatCheese : public Event {
    std::string description = "We eat cheese now";
public:
    using Event::Event;
    std::string id = "EatCheese";

    void go(Flags &flags) override {
        flags.withCheese = true;
        flags.withBiscuits = false;
    };
};

class EatBiscuits : public Event {
    std::string description = "We eat biscuits now";
public:
    using Event::Event;
    std::string id = "EatBiscuits";

    void go(Flags &flags) override {
        flags.withCheese = false;
        flags.withBiscuits = true;
    };
};


class Runner {
public:
    EatCheese eventA;
    EatBiscuits eventB;
    Event *current_event = &eventA; // The problematic line: this still points to Base, not eventA.

    Flags flags;

    Event* step() {
        cout << "description: ";
        current_event->printDescription();
        current_event->go(flags);


        if (current_event->id == "EatCheese") {
            cout << "We have EatCheese"<<endl;
            current_event = &eventB;
        }

        else if (current_event->id == "EatBiscuits") {
            cout << "We have EatBiscuits"<<endl;
            current_event = &eventA;
        }
        cout << endl;

        return current_event;
    }

};


int main() {
    Runner runner;
    cout << "current_event_id: " << runner.current_event->id << ", with biscuits: " << runner.flags.withBiscuits << ", with Cheese: " << runner.flags.withCheese << endl;
    runner.step();
    cout << "current_event_id: " << runner.current_event->id<< ", with biscuits: " << runner.flags.withBiscuits << ", with Cheese: " << runner.flags.withCheese << endl;
    runner.step();
    cout << "current_event_id: " << runner.current_event->id<< ", with biscuits: " << runner.flags.withBiscuits << ", with Cheese: " << runner.flags.withCheese << endl;
    return 0;
};

This will output

current_event_id: Base, with biscuits: 0, with Cheese: 0
description: The base class

current_event_id: Base, with biscuits: 0, with Cheese: 1
description: The base class

current_event_id: Base, with biscuits: 0, with Cheese: 1

Where I expect it to output:

current_event_id: EatCheese, with biscuits: 0, with Cheese: 1
description: We eat cheese now

current_event_id: EatBiscuits, with biscuits: 1, with Cheese: 0
description: We eat biscuits now

current_event_id: EatCheese, with biscuits: 1, with Cheese: 0```

Upvotes: 0

Views: 69

Answers (1)

Timo
Timo

Reputation: 9835

You are referring to non-virtual members in your main function.

struct Base { int myNumber = 0; };

struct Child : Base { int myNumber = 10 };

both classes have a myNumber member but since Child is a derviative of Base it has actually 2 myNumber of which the one from the base class is hidden.

If you do

Child obj;
std::cout << obj.myNumber;

it would print 10, because your expression refers to Child::myNumber. However, if you make it a pointer or reference of your base class

Child obj;
Base* ptr = &obj;
Base& ref = obj;
std::cout << ptr->myNumber << ref.myNumber;

both will print 0 because the object is now treated as type Base. The member variable in question usually hides the base member, but since our variable has a different type now, the access path to that variable is different aswell and Base::myNumber is the symbol we're referring to now.

So create virtual getter functions:

class Event {
    std::string description = "The base class";
public:
    std::string id = "Base";

    virtual std::string const& getDescription() const;
    virtual std::string const& getId() const;
    // ...
};

and override them in your child class.

Also, for further reading I recommend: Why does an overridden function in the derived class hide other overloads of the base class?

Upvotes: 1

Related Questions