patterncppMinor
State pattern + C++ template
Viewed 0 times
patterntemplatestate
Problem
This finds its origin in the following reflection. In their book from 1995, the so-called gang of four (GoF) described the state pattern. What they were actually telling us in their description, is that any object oriented language that implements dynamic polymorphism has an embedded finite state machine (FSM) engine. C++ is such a language, and the example code in the book is written in that language. Why then, do we have to use large frameworks, libraries and tools in order to implement state machines in C++?
I asked myself that question recently for a C++ project where I have the freedom to choose my tools. Since I have long been a supporter of the KISS-principle (keep it simple stupid), my first intention was to just apply the pattern as described in the book.
Basically, the state pattern tells us that the FSM should have a pointer member to a virtual state class, always pointing to a specialization of that class, that represents the current concrete state. It then delegates the events it receives to the state pointer, like this (quoting the book):
where TCPConnection is the FSM and ActiveOpen() is an event.
However, the event handlers look like this:
where TCPClosed and TCPEstablished are concrete states.
Not so neat. I started to wonder if templates couldn't help me to parametrize the target state in
Example FSM
Let's assume that I have the following silly state machine to implement (made with ArgoUML, if anybody is curious).
Silly state machine
It is silly, but it contains the features that, in my experience, are necessary in a state machine implementat
I asked myself that question recently for a C++ project where I have the freedom to choose my tools. Since I have long been a supporter of the KISS-principle (keep it simple stupid), my first intention was to just apply the pattern as described in the book.
Basically, the state pattern tells us that the FSM should have a pointer member to a virtual state class, always pointing to a specialization of that class, that represents the current concrete state. It then delegates the events it receives to the state pointer, like this (quoting the book):
TCPConnection::ActiveOpen () {
_state->ActiveOpen(this);
}where TCPConnection is the FSM and ActiveOpen() is an event.
However, the event handlers look like this:
void TCPClosed::ActiveOpen (TCPConnection* t) {
// send SYN, receive SYN, ACK, etc.
ChangeState(t, TCPEstablished::Instance());
}where TCPClosed and TCPEstablished are concrete states.
Not so neat. I started to wonder if templates couldn't help me to parametrize the target state in
ChangeState(). If the state had both a reference to the FSM and the ability to transform itself as a result of a transition, surely I would get a much simpler and cleaner syntax.Example FSM
Let's assume that I have the following silly state machine to implement (made with ArgoUML, if anybody is curious).
Silly state machine
It is silly, but it contains the features that, in my experience, are necessary in a state machine implementat
Solution
I don't have much to say.
Your implementation is similar to the GoF's (posted here) except that your machine instance is passed by reference to the states' constructors, instead of being passed-in to the states' state-transition methods.
I wonder whether the following would allow a similarly-clean syntax but allow states to be flyweights:
This has some of the same advantages as your scheme (clean state methods) but also allows singleton/flyweight states.
Heap operations can be relatively expensive; and I imagine that some state machines (for example, the tokenizer of a parser) might want to be as fast as possible.
IMO a benefit of your scheme is when the state instances should carry state-specific data. For example, perhaps the TCPEstablished state has associated data which needs to be stored somewhere. If the state is a flyweight then that data must be stored in the machine; but maybe the machine has many states, each with state-specific data, and it's not appropriate for the machine to contain data for the states which it's not in at the moment: in that case you may want state-specific data for the machine in the state instance => state is not a flyweight.
Your implementation is similar to the GoF's (posted here) except that your machine instance is passed by reference to the states' constructors, instead of being passed-in to the states' state-transition methods.
- Advantage: cleaner syntax of the state-transition method
- Disadvantage: state instances can't be flyweights
I wonder whether the following would allow a similarly-clean syntax but allow states to be flyweights:
class LevelState {
public:
virtual LevelState* liftUp() = 0;
virtual LevelState* bringDown() = 0;
};
class HighLevelState : public LevelState {
public:
LevelState* liftUp() { print("already High"); return this; }
LevelState* bringDown() { print("leaving High"); return LowLevelState::enter(); }
static LevelState* enter() { print("entering High"); return &singleton; }
private:
static HighLevelState singleton;
};
class Machine
{
public:
Machine() { levelState = LowLevelState::enter(); }
~Machine() {}
void liftUp() { levelState = levelState->liftUp(); }
void bringDown() { levelState = levelState->bringDown(); }
private:
LevelState* levelState;
};This has some of the same advantages as your scheme (clean state methods) but also allows singleton/flyweight states.
Heap operations can be relatively expensive; and I imagine that some state machines (for example, the tokenizer of a parser) might want to be as fast as possible.
IMO a benefit of your scheme is when the state instances should carry state-specific data. For example, perhaps the TCPEstablished state has associated data which needs to be stored somewhere. If the state is a flyweight then that data must be stored in the machine; but maybe the machine has many states, each with state-specific data, and it's not appropriate for the machine to contain data for the states which it's not in at the moment: in that case you may want state-specific data for the machine in the state instance => state is not a flyweight.
Code Snippets
class LevelState {
public:
virtual LevelState* liftUp() = 0;
virtual LevelState* bringDown() = 0;
};
class HighLevelState : public LevelState {
public:
LevelState* liftUp() { print("already High"); return this; }
LevelState* bringDown() { print("leaving High"); return LowLevelState::enter(); }
static LevelState* enter() { print("entering High"); return &singleton; }
private:
static HighLevelState singleton;
};
class Machine
{
public:
Machine() { levelState = LowLevelState::enter(); }
~Machine() {}
void liftUp() { levelState = levelState->liftUp(); }
void bringDown() { levelState = levelState->bringDown(); }
private:
LevelState* levelState;
};Context
StackExchange Code Review Q#40686, answer score: 9
Revisions (0)
No revisions yet.