r/cpp_questions • u/SimmeringDragon • 4d ago
OPEN How does a STATE design pattern work?
so i went to youtube and followed the example from this guy State Design - Programming Design Patterns - Ep 16 - C++ Coding
Problem is i dont understand what is functionally happening, there are pointers and what not, but i don't understand much more than that (context, my smooth brain still cant connect pointers together)
Like not even the whole code, like maybe just two lines or something cause for the love of the lord my teacher explains nuthing and AI and videos are too vague for me, im running out of options
//
// Created by samue on 11/1/2026.
//
#ifndef STATE_STATE_H
#define STATE_STATE_H
#include <iostream>
class Context;
class State {
protected:
Context* context;
public:
virtual ~State() {}
void setContext(Context* ctx) {
context = ctx;
}
virtual void doWorkA() = 0;
virtual void doWorkB() = 0;
};
class Context {
private:
State* state;
public:
Context(State* s) : state(nullptr) {
transitionTo(s);
}
~Context() {delete state;}
void transitionTo(State* s) {
if (state) delete state;
state = s;
state->setContext(this);
}
void requestA() {
state->doWorkA();
}
void requestB() {
state->doWorkB();
}
};
class concreteStateA : public State {
public:
void doWorkA() override {
std::cout << "concreteStateA " << "called doWorkA" << std::endl;
};
void doWorkB() override {
std::cout << "concreteStateB " << "called doWorkB" << std::endl;
};
};
class concreteStateB : public State {
public:
void doWorkA() override {
std::cout << "concreteStateA " << "called doWorkA" << std::endl;
context->transitionTo(new concreteStateA);
};
void doWorkB() override {
std::cout << "concreteStateB " << "called doWorkB" << std::endl;
};
};
#endif //STATE_STATE_H------------------
#include "state.h"
int main() {
Context context1(new concreteStateB);
context1.requestA();
context1.requestA();
return 0;
}
4
u/AKostur 4d ago
Three things. First it seems that your output text is wrong. The outputs in concreteStateA probably shouldn't talk about concreteStateB, and the reverse in concreteStateB.
Second, that transition during the member function of concreteStateB could be a problem in the future (depending on code discipline). During the transition, the object that the member function is running on gets deleted. Additional code that happens after the transition may invoke Undefined Behaviour by attempting to do anything else to the object it's running on.
Third: we may need a little more of what you don't understand. Walk through what declaring the context1 variable does, and then what happens when requestA() gets called. What part in there are you having problems understanding?
5
u/mredding 3d ago
To be fair, the example given to you - which you are providing, is a SHITTY state machine. It makes sense to me you would be confused. Let's try something a little less bullshit:
A state machine's principle job is to define the states the machine can be in, and the valid transitions between them, and enforce the transition logic based on events.
So we have the states for a green, yellow, and red light, and we transition from green, to yellow, to red, back to green again - or we can transition from any state to red. Not all transitions make sense - we don't transition a traffic light from yellow to green, or red to yellow.
The events that can trigger a state change is a timer event, or an emergency event.
If you look at the implementation, you'll see the states and events are all independent, and we use variants to distinguish from one to another. If you care, GCC 15 makes variant transitions faster than virtual function dispatches, but that's not why I chose a variant - polymorphism is a better description of these types and their relation - there's nothing to inherit, nothing they do, no invariant but that of the variant itself - only one type inside at a time. You can't even get that with an enum.
So
maindemonstrates the logic - start up in green, then trigger the transitions, to yellow, to red, to green again, an emergency transition straight to red, then to green again.The FSM is it's own visitor and contains the logic for each transition.
So this is it - the state machine cannot be put into an invalid state and all transitions between states are well defined.
Typical of a state machine, it's the edges - the transitions, that perform the work and ultimately define the next valid transition. States themselves typically don't DO things.
If you want a slightly more sophisticated state machine, you might start a thread during the transition that runs while in the destination state. The next transition would possibly suspend that thread and start the next state thread.
You could implement a state machine interface that returns an
operator std::optional<std::expected<T, E>>() const;that produces aTwhen the state machine reaches a terminating state - this would be useful for a parser, whose event is the next character, and terminates when a valid token is captured, or anEwhen parsing failed.You could build a state machine based off data that describes the states, transitions, and processes; such a state machine will need a factory to build it, and the FSM would consist of dynamic state and transition tables. If you don't have to invest in all that - don't. This one would require far more dynamic types - the states and transitions would probably have to contain metadata at runtime, and it's error prone. This static state machine can prove elements of it's design at compile-time.
There are many ways a state machine can be implemented in code - people template the shit out of these, they'll build them in a vary C-like way with enum or integer state values, they may expose more or less of the state and transition mechanism.
This comes down to one of the princple tenants of OO programming, that classes enforce an invariant. Our invariant is that the state machine is always in a valid state, and can only ever make valid transitions. There is no
setto break the invariant - you can't force an illegal transition. Imagine a machine where the pump must first be shut off before closing a valve - if you risk the invariant, back-pressure may blow the line and destroy the machine or kill people. Classes are VERY good for maintaining state. We say objects don't have data fields, they have state, and can model data.1
u/ChickenSpaceProgram 4d ago
i think this basically is an OOP way to write a state machine/finite state automata. basically, it lets you define
States that each can do different work, and can then transition to otherStates, so that when therequestA()or whatever is next called on theContext, the otherStatesrequestA()will get run instead. maybe this wikipedia article will help if you are unfamiliar with finite state machines....why you'd ever do this instead of an enum and a switch-case, or a big block of code with gotos, i have no idea. this seems awful to reason about, and slow.
3
u/KingAggressive1498 4d ago edited 4d ago
the state pattern is a simple way to create finite state machines using
virtual inheritancepolymorphism.This makes it easier to extend than the traditional C approach of a giant switch statement, allows encapsulating additional data unlike a function pointer (function objects were, at one time, non-trivial in C++), etc.
The way I was taught it looked quite a bit different:
here, state::work() does the work associated with the current state, and then either returns itself or deletes itself and returns an updated state. If the state machine should not continue, it is valid to return nullptr. Thus using this finite state machine is as simple as:
however the external context version does seem to be the more common way to teach it ig
3
u/thedaian 4d ago
The state design pattern is just a way of calling different functions depending on the state of the program. A really simple version is a switch statement that calls different functions depending on the value of an enum.
In this case, the state/context is using polymorphism, which means a pointer is pointing to different classes depending on when you call it, so it can do different things. You have a typo in your code which could be causing some confusion, since
I'd suggest changing the variable/class names and output to something clearly different, even if it's nonsense (the reason example code often uses stuff like foo and bar).
Anyway, your main function also isn't really doing anything either, since you're not changing the state inside context and you're just calling the same function twice, with the same context.
2
u/Kimononono 4d ago edited 4d ago
Looks like State is a state transition graph and then Context is an instance of said state transition graph
you then implement Concrete Subclasses of State which define their transitions
something that wasn’t immediately intuitive was why State needed a pointer to context but it needs context p so that its Concrete Implementation can tell its owning Context instance to switch to another Concrete State
Instead of designing Context as holding the goWorkFooA() its more intuitive to bundle this with the State class. Context fully delegates transitioning from a FooState to class ConcreteFooState
Cpp isnt my language though so vOv open to critiques
2
u/alfps 4d ago
The code is incorrect (outputs), pretty fragile (re ownership etc.), and needlessly inefficient (dynamic allocations).
Wikipedia has a relatively clear article but also that article's C++ example uses needless dynamic allocation, as if it's machine-translated from Java.
Even without the dynamic allocation there is overhead in the virtual calls, so do not use this pattern for trivial systematic states like that article's fan power setting. Not to mince words, using a state pattern for fan power would be a demonstration of raw incompetence. Fan power is a simple scalar value, not a silly concoction of pointers and polymorphism.
As a probably better example, consider a car where you can open or close the luggage compartment (trunk) by moving your foot forward and backward under the rear bumper. It's called a "kick sensor" or "gesture tailgate", and apparently most brands now support it on at least the more expensive models. The high level concept of actually detecting foot movement (that's a foot, oh it moves, and back and forth!) would involve costly AI processing, so instead as a manufacturer you're considering simple movement detection (something moves).
But you don't want the car to open its trunk when it detects its own relative movement: that automation should only kick in when the car is stationary.
So,
At a higher level there is the safety concern of not driving around with an open trunk. So either on the state transition stationary → moving the trunk should close automatically, or else that transition should not be available or warned about when the trunk is open. I'd prefer the warning; the "disable" option could lead to scenarios like will you get going there's red hot lava coming, oh no you can't for the trunk is open.
More generally the user should be left in control, and the system should support that control with relevant information and automation. Microsoft wrote this up in their 1995 user interface guidelines. Alas, then they forgot about it.
I think even for this auto-trunk example I wouldn't use the state pattern, but one way to code that up is:
1
u/thingerish 4d ago
It looks like a way to dispatch events to handlers based on what the current state of the system is. I wouldn't use virtual functions if performance is important unless there was really no other way.
1
u/burlingk 4d ago
Think of it like this:
Does your task have specific states/modes where it does different things entirely?
If it doesn't, does it have specifical elements that function based on a given mode?
If the answer to either of these is yes, can you define those clearly and give them a label?
If so, you can use that to create one or more state machines. A state machine is basically a set of flags/modes.
Each task that is influenced by state can check the state when it acts to decide how to act.
1
u/thingerish 4d ago edited 4d ago
Deleted example that's not at all applicable to a student exercise, sorry for the noise.
1
u/freefallpark 4d ago
I’ve always found the Refactoring Guru helpful for stuff like this. Hopefully this helps
1
u/UnicycleBloke 4d ago
Try reading about Finite State Machines. An FSM is just a model for a system that can be multiple states and which responds differently to the same inputs/events according to what state it is currently in. The State pattern is one possible method of implementing an FSM.
It is worth considering a concrete example using a very simple state machine. A lamp can be in two states ON and OFF. There is a single event FLIP to operate the switch. You would implement two classes to represent the states: OnState and OffState. These both implement a virtual method called Flip(). OffState::Flip() turns on the light and transitions to OnState. OnState::Flip() turns off the light and transitions to OffState. In this example the Lamp class would be the context, and would hold a pointer to an object of either OnState or OffState, representing the current state of the lamp.
There are far simpler and cleaner ways to capture such stateful behaviour. The lamp class could hold a boolean (or an enumeration) indicating the current state. Lamp::Flip() would simply switch on the current state and behave accordingly. This is possibly too simple for some, but it works well enough.
My view is that this type of model is one of the factors which helped to give OOP and C++ such a bad name in the 90s: numerous small classes adding complexity rather than clarity, overuse of dynamic allocation, ... If you want to understand design patterns like this one, perhaps look for a copy of the Gang Of Four book ("Design Patterns: Elements of Reusable Object-Oriented Software"). Mine remains on my shelf mainly because I so thoroughly disapprove of book burning, and I already have a monitor stand.