6
\$\begingroup\$

This is a variant of the solution posted here, without state creation/destruction penalty.

Note that my comment in the original question about the simplification provided by C++11's inherited constructors applies here too, but I have not updated the code in this question accordingly. That is left as an exercise to the reader.

The variant

I like the event/action syntax of the solution I proposed in my question. However, I really dislike the runtime penalty of creating/destroying states on the fly (I have made no measurement, but I dislike the principle).

I present hereafter a variant where the event/action syntax simplicity is pretty much kept (although slightly modified), but all states are created in advance. To keep my syntax, the states obviously need to be context-aware, which makes my solution anti-flyweight (one instance of every single concrete state per instance of a state machine). My feeling is that the cost in RAM is more affordable to the applications I have in mind than the runtime penalty. Another cost of this variant is a more complex declaration for states: declaration of state instances, need for a state to refer not only to the state machine but also to the nearest superstate.

Obviously, a straight application of the GoF's state pattern is an alternative for applications that need both flyweight and runtime efficiency (at the expense of event/action syntax simplicity).

Please comment on this.

genericstate.h

#ifndef GENERICSTATE_H
#define GENERICSTATE_H

template <typename StateMachine, class State, class SuperState = StateMachine>
class GenericState
{
public:
    static void init(State *&state, State &initState) {
        state = &initState;
        initState.entry();
    }

protected:
    explicit GenericState(StateMachine &m, State *&state) :
        m(m), s(m), state(state) {}

    explicit GenericState(StateMachine &m, State *&state, SuperState &s) :
        m(m), s(s), state(state) {}

    void change(State &targetState) {
        exit();
        init(state, targetState);
    }

private:
    virtual void entry() {}
    virtual void exit() {}

protected:
    StateMachine &m;
    SuperState &s;

private:
    State *&state;
};

#endif // GENERICSTATE_H

machine.h

#ifndef MACHINE_H
#define MACHINE_H

#include <string>
#include <iostream>

#include "genericstate.h"

class Machine
{
public:
    Machine() :
        high(*this, levelState), low(*this, levelState),
        left(*this, directionState), right(*this, directionState) {}
    ~Machine() {}
    void start();

public:
   enum Color {
       BLUE,
       RED
   };

public:
   void liftUp() { levelState->liftUp(); }
   void bringDown() { levelState->bringDown(); }
   void paint(Color color) { directionState->paint(color); }
   void turnRight() { directionState->turnRight(); }
   void turnLeft() { directionState->turnLeft(); }

private:
    static void print(const std::string &str) { std::cout << str << std::endl; }
    static void unhandledEvent() { print("unhandled event"); }
    void changedColor() { print("changed color"); }

private:
    class LevelState : public GenericState<Machine, LevelState> {
    protected:
        explicit LevelState(Machine &m, LevelState *&state) :
            GenericState<Machine, LevelState>(m, state) {}
    public:
        virtual void liftUp() { unhandledEvent(); }
        virtual void bringDown() { unhandledEvent(); }
    } *levelState;

    struct High : public LevelState {
        explicit High(Machine &m, LevelState *&state) :
            LevelState(m, state) {}
        void entry() { print("entering High"); }
        void liftUp() { print("already High"); }
        void bringDown() { change(s.low); }
        void exit() { print("leaving High"); }
    } high;

    struct Low : public LevelState {
        explicit Low(Machine &m, LevelState *&state) :
            LevelState(m, state) {}
        void entry() { print("entering Low"); }
        void liftUp() { change(s.high); }
        void bringDown() { print("already Low"); }
        void exit() { print("leaving Low"); }
    } low;

private:
    class Left;

    class ColorState : public GenericState<Machine, ColorState, Left> {
    protected:
        explicit ColorState(Machine &m, ColorState *&state, Left &s) :
            GenericState<Machine, ColorState, Left>(m, state, s) {}
    public:
        virtual void paint(Color color) { (void)color; unhandledEvent(); }
    };

    struct Red : public ColorState {
        explicit Red(Machine &m, ColorState *&state, Left &s) :
            ColorState(m, state, s) {}
        void entry() { m.changedColor(); }
        void paint(Color color);
    };

    struct Blue : public ColorState {
        explicit Blue(Machine &m, ColorState *&state, Left &s) :
            ColorState(m, state, s) {}
        void entry() { m.changedColor(); }
        void paint(Color color);
    };

private:
    class DirectionState : public GenericState<Machine, DirectionState> {
    protected:
        explicit DirectionState(Machine &m, DirectionState *&state) :
            GenericState<Machine, DirectionState>(m, state) {}
    public:
        virtual void paint(Color color) { (void)color; unhandledEvent(); }
        virtual void turnRight() { unhandledEvent(); }
        virtual void turnLeft() { unhandledEvent(); }
    } *directionState;

    struct Left : public DirectionState {
        friend class Red;
        friend class Blue;
        explicit Left(Machine &m, DirectionState *&state) :
            DirectionState(m, state),
            red(m, colorState, *this), blue(m, colorState, *this) {}
        void entry() { ColorState::init(colorState, red); }
        void paint(Color color) { colorState->paint(color); }
        void turnRight() { change(s.right); }
    private:
        ColorState *colorState;
        Red red;
        Blue blue;
    } left;

    struct Right : public DirectionState {
        explicit Right(Machine &m, DirectionState *&state) :
            DirectionState(m, state) {}
        void turnLeft() { change(s.left); }
    } right;
};

#endif // MACHINE_H

machine.cpp

#include "machine.h"

void Machine::start()
{
    LevelState::init(levelState, high);
    DirectionState::init(directionState, left);
}

void Machine::Red::paint(Machine::Color color)
{
     if (color == BLUE) change(s.blue);
     else ColorState::paint(color);
}

void Machine::Blue::paint(Machine::Color color)
{
     if (color == RED) change(s.red);
     else ColorState::paint(color);
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I'm not too familiar with inheritance, but here are some stylistic things I've noticed:

  • There is no need to use the public, private, and protected keywords repeatedly in the same class declaration. It just makes it harder to read and adds nothing of value. You should just use the needed keywords once, and you can optionally leave out the private keyword since classes are private by default (this may also be less-readable overall, but it's up to you).

    This may also be an indication that you have a little too much in your class declarations, as you are trying to organize everything. You may consider putting these inherited structs into separate files and then forward-declaring them in the needed classes after including them in the headers.

  • machine.h:

    Member functions that are not supposed to modify data members, such as print(), should be const to prevent any accidental modifications. It probably also doesn't need to be static.

    void print(const std::string &str) const { std::cout << str << std::endl; }
    

    These function names are misleading as they don't perform any of these actions:

    void entry() { print("entering High"); }
    void bringDown() { print("already Low"); }
    void exit() { print("leaving Low"); }
    void entry() { print("entering Low"); }
    void bringDown() { print("already Low"); }
    void exit() { print("leaving Low"); }
    

    If you still need these functions, despite the fact that they only print some hard-coded text, then at least give them more accurate names to indicate that they're printing something and not actually performing an action.

  • machine.cpp:

    This is not a very maintainable style:

    if (color == BLUE) change(s.blue);
    else ColorState::paint(color);
    

    In case you may need to add additional lines, use curly braces:

    if (color == BLUE)
    {
        change(s.blue);
    }
    else
    {
        ColorState::paint(color);
    }
    

    This should also be done with the other related function.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.