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