Published: Updated:

In one project, I decided to navigate through the steps of the installation wizard using a state machine.

First example

Obviously the first thing to do is go to Stack Overflow and find this answer with finite state machine code example. It simulates a vending machine.

I visit this repository. I like the code style, structure, and I want to contribute ❤️

I add CMake build system.

cmake_minimum_required(VERSION 3.10)

project(cpp-state-machine VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(PROJECT_SOURCES
    main.cpp
    Machine.cpp
    MachineStates.cpp
)

add_executable(vending-machine ${PROJECT_SOURCES})

I switch to smart pointers, the state now is held by the unique_ptr and destructors become empty. You can check all changes in my fork.

class Machine {
public:
  Machine(unsigned int _stock);
  ~Machine() = default;
private:
  std::unique_ptr<AbstractState> state;
};

Machine::Machine(unsigned int _stock) : stock(_stock) {
  state = stock > 0 ? std::unique_ptr<AbstractState>{std::make_unique<Normal>()}
                    : std::make_unique<SoldOut>();
}

void AbstractState::setState(Machine &machine, AbstractState *state) {
  machine.state.reset(state);
}

I add override keywords. I check that the base destructor is virtual, because that's how it must be, and it is the trivial destructor which I mark using the default keyword.

-  virtual void sell(Machine &machine, unsignd int quantity);
-  virtual void refill(Machine &machine, unsigned int quantity);
-  virtual void fix(Machine &machine);
-  virtual ~Normal();
+  virtual void sell(Machine &machine, unsigned int quantity) override;
+  virtual void refill(Machine &machine, unsigned int quantity) override;
+  virtual void fix(Machine &machine) override

But I want to push it further. Do you know the trick how to restrict users of your interface from using destructors directly? You make the destructor protected. Protected, but not virtual this time.

#include <iostream>

class ProtectedDestructor {
public:
    ProtectedDestructor() {
        std::cout << "[ProtectedDestructor] constructor" << std::endl;
    }
protected:
    ~ProtectedDestructor() {
        std::cout << "[ProtectedDestructor] destructor" << std::endl;
    }
};

int main(int argc, char const *argv[])
{
    ProtectedDestructor* obj = new ProtectedDestructor();
    delete obj; // error: "ProtectedDestructor::~ProtectedDestructor()" (declared at line 9) is inaccessible
    return 0;
}

But it left me stymied.

Dynamic state machine

But I realize, that I need a dynamic state machine, because I don't know how many nodes I need in it before compilation time.

Some good finding that looks good, but didn't help.

For factory class I used ...-feature (parameter pack) which in simple words does: and the rest just pass to the next interested party.

#ifndef STATEFACTORY_H
#define STATEFACTORY_H

#include <memory>
#include "stagestate.h"

class StateFactory
{
public:
    template<typename... Types>
    std::shared_ptr<State> create(Types ... args) {
        return std::make_shared<State>(args...);
    }
};

#endif // STATEFACTORY_H

Base state

#ifndef BASESTATE_H
#define BASESTATE_H

#include <memory>
#include <string>

class BaseState
{
public:
    BaseState() = delete;
    BaseState(int id, const std::string& stateName) :
        id(id),
        name(stateName)
    {}
    virtual ~BaseState() {}

    int getId() const;
    std::string getName() const;

    void next();
    void back();
    void error();
    static std::shared_ptr<BaseState> errorState;

protected:
    virtual void doNext(std::shared_ptr<BaseState>& state) {};
    virtual void doBack(std::shared_ptr<BaseState>& state) {};
    void setState(std::shared_ptr<BaseState> state);

private:
    int id;
    std::string name;
};

#endif // BASESTATE_H
#ifndef STATE_H
#define STATE_H

#include <memory>
#include <string>
#include "basestate.h"

class State : public BaseState
{
public:
    State(int id, const std::string& name) :
        BaseState(id, name)
    {}

    std::shared_ptr<State> prevState;
    std::shared_ptr<State> nextState;

protected:
    virtual void doNext(std::shared_ptr<BaseState>& state) override;
    virtual void doBack(std::shared_ptr<BaseState>& state) override;
};

#endif // STATE_H

And implementation

std::list<std::shared_ptr<State>> stages;

auto factory = std::make_unique<StateFactory>();
    std::shared_ptr<State> prevState;
    for () {
    std::shared_ptr<State> state = factory->create(stepId, names.first);
    if (stages.empty()) {
            prevState = state;
        } else {
            state->prevState = prevState;
            if (new_group) {
                prevState = state;
            }
            stages.back()->nextState = state;
        }
        stages.push_back(state);
    }

// TODO: create a list of bars to visit and the final state where you go home


Similar posts

Rate this page