dimanche 31 juillet 2022

Problem with implementation of the hierarchical state machine in C++

I have been attempting to implement a hierarchical state machine in C++. I have chosen the approach based on the State design pattern and the Composite design pattern. According to the Composite design pattern I have defined a common abstract base class for the atomic states (simple states) and the composite states (states consisting of state machines)

State.h

class State {
public:
    virtual void notifyCatModeRequested(bool state) = 0;
    virtual void notifyBatModeRequested(bool state) = 0;
    virtual void notifyMainContactorsCloseRequested(bool state) = 0;
    virtual void update() = 0;
};

StateAtomic.h

#include "State.h"
#include "StateComposite.h"

class StateAtomic : public State {
public:
    StateAtomic(StateComposite* parent) : parent(parent) {}
    void notifyCatModeRequested(bool state) {cat_mode_requested = state;}
    void notifyBatModeRequested(bool state) {bat_mode_requested = state;}
    void notifyMainContactorsCloseRequested(bool state) {main_contactors_close_requested = state;}
    virtual void update() = 0;

protected:
    StateComposite *parent;
    bool cat_mode_requested;
    bool bat_mode_requested;
    bool main_contactors_close_requested;
};

StateComposite.h

class StateComposite : public State {
public:
    StateComposite(StateComposite* parent) : parent(parent) {}
    void notifyCatModeRequested(bool state) {active->notifyCatModeRequested(state);}
    void notifyBatModeRequested(bool state) {active->notifyBatModeRequested(state);}
    void notifyMainContactorsCloseRequested(bool state) {main_contactors_close_requested = state;}
    void update() {active->update();}
    void switchState(State * new_state) {active = new_state;}
    
protected:
    StateComposite* parent;
    State* active;
};

According to the State design pattern, I have defined a class for the individual states of the state machine

Ready.h

#include "StateAtomic.h"

class StateMachine;

class Ready : public StateAtomic {
    public:
        Ready(StateMachine *parent) : : StateAtomic(parent) {}
        void update() {
            std::cout << "Ready" << std::endl;
            if (cat_mode_requested && main_contactors_close_requested) {
                parent->switchState(& static_cast<StateMachine*>(parent)->cat);
                std::cout << "Switch to cat." << std::endl;
            } else if (bat_mode_requested && main_contactors_close_requested) {
                parent->switchState(& static_cast<StateMachine*>(parent)->bat);
                std::cout << "Switch to bat." << std::endl;
            }
    }
};

Then I have defined the top-level state machine

#include "StateComposite.h"
#include "StateAtomic.h"
#include "Ready.h"
#include <iostream>

class StateMachine : public StateComposite {
public:

    StateMachine() : StateComposite(nullptr), ready(this) {
        active = &ready;
    }
    void update() {active->update();}

private:

    Ready ready;
    Cat cat;

    friend class Ready;
    friend class Cat;
};

And a very simple application

main.cpp

#include "StateMachine.h"
#include <cstdlib>

using namespace std;

struct command {
    bool cat_mode_requested;
    bool bat_mode_requested;
    bool main_contactors_close_requested;
};

int main(int argc, char** argv) {

    StateMachine state_machine;
    command active_command;

    for (uint8_t i = 0; i < 3; i++) {

        std::cout << "Cat mode requested?" << std::endl;
        std::cin >> active_command.cat_mode_requested;
        std::cout << "Bat mode requested?" << std::endl;
        std::cin >> active_command.bat_mode_requested;
        std::cout << "Main contactors close requested?" << std::endl;
        std::cin >> active_command.main_contactors_close_requested;

        state_machine.notifyCatModeRequested(active_command.cat_mode_requested);
        state_machine.notifyBatModeRequested(active_command.bat_mode_requested);
        state_machine.notifyMainContactorsCloseRequested(active_command.main_contactors_close_requested);
        state_machine.update();
    }
}

I have found that the following steps result in program crash:

  1. Invoking the state_machine.update()
  2. Invoking parent->switchState(& static_cast<StateMachine*>(parent)->cat) inside the Ready::update()
  3. Invoking the state_machine.update()

The problem is obviously in this statement: parent->switchState(& static_cast<StateMachine*>(parent)->cat) called from the Ready::update().

Unfortunately, I don't understand why. Can anybody help me understand what I am doing wrong? Thanks in advance.

Aucun commentaire:

Enregistrer un commentaire