Home > database >  Problem with using derived class where base class is expected
Problem with using derived class where base class is expected

Time:07-15

I am writing a code for a little system that should run on an arduino. The objective is to control several cycles which each have a certain amount of sub-cycles. Both the cycles and the subcycles are defined by their duration and ultimately, the system's operations will be performed at the subcycle level (didn't implement this yet).

I thought about creating a class that could manage those events called EventManager. A class to represent the subevents called Events and a class called Cycles which is derived from both classes because it is both an event and at the same time will manage the subevents.

My problem is that when I pass Event objects to the EventManager, all is good. However, when I pass the Cycle objects, it doesn't work as expected (see attached pictures... Cycle 2 name isn't initialized for some reason).

This is my code below and please bare with me, I know it's a lot of files. Any help would be greatly appreciated. Thank you!

EventManager.h

#ifndef SRC_EVENTMANAGER
#define SRC_EVENTMANAGER

#include <Arduino.h>
#include "Event.h"

class EventManager
{
public:
    EventManager(size_t n_events = 0, Event *events = nullptr);
    void loop();

private:
    size_t n_events;
    Event *events;
    size_t current_event;
    bool cycle_ended;
    bool check_event_end();
    void end_current_event();
};

#endif /* SRC_EVENTMANAGER */

EventManager.cpp

#include "EventManager.h"

EventManager::EventManager(size_t n_events, Event *events)
    : n_events(n_events), events(events), current_event(0), cycle_ended(false) {}

bool EventManager::check_event_end()
{
    return events[current_event].ended();
}

void EventManager::end_current_event()
{
    events[current_event].end();
    current_event == n_events - 1 ? cycle_ended = true : current_event  ;
}

void EventManager::loop()
{
    if (cycle_ended)
        return;

    events[current_event].run();

    if (check_event_end())
        end_current_event();
}

Event.h

#ifndef SRC_EVENT
#define SRC_EVENT

#include <Arduino.h>
#include "utils.h"

class Event
{
public:
    Event(String name, Duration duration);
    bool ended();
    void start();
    void run();
    void end();

private:
    String name;
    unsigned long duration;
    unsigned long start_time;
    unsigned long end_time;
    bool started;
};

#endif /* SRC_EVENT */

Event.cpp

#include "Event.h"

Event::Event(String name, Duration duration)
    : name(name), start_time(0), end_time(0), started(false)
{
    this->duration = duration.toMillis();
}

bool Event::ended()
{
    return millis() >= end_time;
}

void Event::start()
{
    if (started)
        return;
    start_time = millis();
    end_time = start_time   duration;
    Serial.println("Event "   name   " started.");
    started = true;
}

void Event::end()
{
    start_time = 0;
    end_time = 0;
    started = false;
    Serial.println("Event "   name   " ended.");
}

void Event::run()
{
    start();
    // Event logic here
}

Cycle.h

#ifndef SRC_CYCLE
#define SRC_CYCLE

#include <Arduino.h>
#include "Event.h"
#include "EventManager.h"

class Cycle : public Event, public EventManager
{
public:
    Cycle(String name, Duration duration, size_t event_count, Event *events);
};

#endif /* SRC_CYCLE */

Cycle.cpp

#include "Cycle.h"

Cycle::Cycle(String name, Duration duration, size_t event_count, Event *events)
    : Event(name, duration), EventManager(event_count, events){}

main.cpp

#include <Arduino.h>
#include "EventManager.h"
#include "Event.h"
#include "Cycle.h"

Event events[] = {
    Event("event1", Duration{0, 0, 5}),
    Event("event2", Duration{0, 0, 5}),
};

Cycle cycles[] = {
    Cycle("First cycle", Duration{0, 0, 10}, 2, events),
    Cycle("Second cycle", Duration{0, 0, 10}, 2, events),
};

EventManager event_manager(2, cycles);
// EventManager event_manager(2, events);

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  event_manager.loop();
}

Broken example with Cycle object

Working example with Event object

CodePudding user response:

You aren't passing an event or event pointer to your event manager; you are passing an array of events. While accessing individual objects through a pointer is polymorphic, this does not extend to raw arrays. Raw arrays are simple collections of only 1 type of object (naturally all of the same size). And all they contain is the objects - not the type or number of entries. (More favored in modern C are the fancier std::array and std::vector which you might want to look into, but won't solve your immediate problem here.)

You can't substitute an array of derived class for an array of base class. While objects accessed via pointer behave polymorphically, an array of a derived class is not a sub type of an array of a base class. If you pass a raw array, as you are doing, by pointer to first element and number of elements, that is fine as the size of each element is implicit in the element type. If you substitute in an array of a derived type, the compiler will be calculating the wrong address for any element after the first. You then get a hearty helping of scrambled data. I should also mention that modern C isn't particularly welcoming to unsanctioned type-punning games.

What you can do is set things up to pass an array of base pointers to derived objects. That is entirely legitimate and keeps to the same level language features that you are currently using. This adds an extra level of indirection and an intermediate array to your design.

To try to illustrate this suggestion, some key modified snippets from your code keeping things simple and close to your code (but not updating all of it):

EventManager.h

#ifndef SRC_EVENTMANAGER
#define SRC_EVENTMANAGER

#include <Arduino.h>
#include "Event.h"

class EventManager
{
public:
    EventManager(size_t n_events = 0, Event **eventPointers = nullptr); // changed 
    void loop();

private:
    size_t n_events;
    Event **eventPointers;  // changed 
    size_t current_event;
    bool cycle_ended;
    bool check_event_end();
    void end_current_event();
};

#endif /* SRC_EVENTMANAGER */

partial EventManager.cpp

#include "EventManager.h"

EventManager::EventManager(size_t n_events, Event **eventPointers)
    : n_events(n_events), eventPointers(eventPointers), current_event(0), cycle_ended(false) {}
    // changed    
bool EventManager::check_event_end()
{
    return eventPointers[current_event]->ended();  // changed
}

Event.h

#ifndef SRC_EVENT
#define SRC_EVENT

#include <Arduino.h>
#include "utils.h"

class Event
{
public:
    Event(String name, Duration duration);
    virtual bool ended();    // make functions virtual as needed
    virtual void start();
    virtual void run();
    virtual void end();

private:
    String name;
    unsigned long duration;
    unsigned long start_time;
    unsigned long end_time;
    bool started;
};

#endif /* SRC_EVENT */

main.cpp

#include <Arduino.h>
#include "EventManager.h"
#include "Event.h"
#include "Cycle.h"

Event events[] = {
    Event("event1", Duration{0, 0, 5}),
    Event("event2", Duration{0, 0, 5}),
};

Event *eventsPointers[] = {    // added
    &events[0],
    &events[1],
};


Cycle cycles[] = {
    Cycle("First cycle", Duration{0, 0, 10}, 2, events),
    Cycle("Second cycle", Duration{0, 0, 10}, 2, events),
};

Event *cyclesPointers[] = {  // added, note using base pointers
    &cycles[0],
    &cycles[1],
};

EventManager event_manager(2, cyclesPointers);
// EventManager event_manager(2, eventsPointers);
  • Related