C++ State Pattern: Complete Guide | Finite State Machines & Behavior
이 글의 핵심
State pattern in C++: context delegates to state objects for clean transitions—examples from networking, games, and vending machines.
Compare Strategy vs State alongside other behavioral patterns in C++ behavioral patterns #20-1 and overview #20-2.
What is State Pattern? why you need it
Problem Scenario: Exploding conditional statement
Problem: If the behavior depends on the state of the object, you have a huge if-else.
// Bad example: exploding conditional statement
class TCPConnection {
enum State { CLOSED, LISTEN, ESTABLISHED };
State state = CLOSED;
void open() {
if (state == CLOSED) {
// Transition to LISTEN
} else if (state == LISTEN) {
// already opened
} else if (state == ESTABLISHED) {
// error
}
}
void close() {
if (state == CLOSED) {
// error
} else if (state == LISTEN) {
// Transition to CLOSED
} else if (state == ESTABLISHED) {
// Transition to CLOSED
}
}
};
Solution: State Pattern encapsulates each state into a class. Context holds the current State object and delegates requests to the State.
// Good example: State Pattern
class TCPState {
public:
virtual void open(TCPConnection& conn) = 0;
virtual void close(TCPConnection& conn) = 0;
virtual ~TCPState() = default;
};
class ClosedState : public TCPState {
void open(TCPConnection& conn) override {
conn.setState(std::make_unique<ListenState>());
}
void close(TCPConnection& conn) override {
// error
}
};
class TCPConnection {
std::unique_ptr<TCPState> state;
public:
void open() { state->open(*this); }
void close() { state->close(*this); }
void setState(std::unique_ptr<TCPState> s) { state = std::move(s); }
};
stateDiagram-v2
[*] --> Closed
Closed --> Listen: open()
Listen --> Established: accept()
Established --> Closed: close()
Listen --> Closed: close()
index
- Basic structure
- State transition diagram
- Game AI example
- Frequently occurring problems and solutions
- Production Patterns
- Complete example: vending machine
1. basic structure
#include <iostream>
#include <memory>
class TrafficLight;
class State {
public:
virtual void handle(TrafficLight& light) = 0;
virtual std::string name() const = 0;
virtual ~State() = default;
};
class TrafficLight {
public:
TrafficLight();
void setState(std::unique_ptr<State> s) {
state = std::move(s);
std::cout << "State: " << state->name() << '\n';
}
void change() {
state->handle(*this);
}
private:
std::unique_ptr<State> state;
};
class RedState : public State {
public:
void handle(TrafficLight& light) override;
std::string name() const override { return "Red"; }
};
class GreenState : public State {
public:
void handle(TrafficLight& light) override;
std::string name() const override { return "Green"; }
};
class YellowState : public State {
public:
void handle(TrafficLight& light) override;
std::string name() const override { return "Yellow"; }
};
void RedState::handle(TrafficLight& light) {
light.setState(std::make_unique<GreenState>());
}
void GreenState::handle(TrafficLight& light) {
light.setState(std::make_unique<YellowState>());
}
void YellowState::handle(TrafficLight& light) {
light.setState(std::make_unique<RedState>());
}
TrafficLight::TrafficLight() {
state = std::make_unique<RedState>();
std::cout << "Initial state: " << state->name() << '\n';
}
int main() {
TrafficLight light;
light.change(); // Red -> Green
light.change(); // Green -> Yellow
light.change(); // Yellow -> Red
}
2. state transition diagram
TCP connection status
#include <iostream>
#include <memory>
class TCPConnection;
class TCPState {
public:
virtual void open(TCPConnection& conn) = 0;
virtual void close(TCPConnection& conn) = 0;
virtual void acknowledge(TCPConnection& conn) = 0;
virtual std::string name() const = 0;
virtual ~TCPState() = default;
};
class TCPConnection {
public:
TCPConnection();
void setState(std::unique_ptr<TCPState> s) {
state = std::move(s);
std::cout << "State: " << state->name() << '\n';
}
void open() { state->open(*this); }
void close() { state->close(*this); }
void acknowledge() { state->acknowledge(*this); }
private:
std::unique_ptr<TCPState> state;
};
class ClosedState : public TCPState {
public:
void open(TCPConnection& conn) override;
void close(TCPConnection& conn) override {
std::cout << "Already closed\n";
}
void acknowledge(TCPConnection& conn) override {
std::cout << "Error: not open\n";
}
std::string name() const override { return "Closed"; }
};
class ListenState : public TCPState {
public:
void open(TCPConnection& conn) override {
std::cout << "Already listening\n";
}
void close(TCPConnection& conn) override;
void acknowledge(TCPConnection& conn) override;
std::string name() const override { return "Listen"; }
};
class EstablishedState : public TCPState {
public:
void open(TCPConnection& conn) override {
std::cout << "Already established\n";
}
void close(TCPConnection& conn) override;
void acknowledge(TCPConnection& conn) override {
std::cout << "Data transfer...\n";
}
std::string name() const override { return "Established"; }
};
void ClosedState::open(TCPConnection& conn) {
conn.setState(std::make_unique<ListenState>());
}
void ListenState::close(TCPConnection& conn) {
conn.setState(std::make_unique<ClosedState>());
}
void ListenState::acknowledge(TCPConnection& conn) {
conn.setState(std::make_unique<EstablishedState>());
}
void EstablishedState::close(TCPConnection& conn) {
conn.setState(std::make_unique<ClosedState>());
}
TCPConnection::TCPConnection() {
state = std::make_unique<ClosedState>();
std::cout << "Initial state: " << state->name() << '\n';
}
int main() {
TCPConnection conn;
conn.open(); // Closed -> Listen
conn.acknowledge(); // Listen -> Established
conn.close(); // Established -> Closed
}
3. Game AI Example
#include <iostream>
#include <memory>
class Enemy;
class AIState {
public:
virtual void update(Enemy& enemy) = 0;
virtual std::string name() const = 0;
virtual ~AIState() = default;
};
class Enemy {
public:
Enemy(int hp, int dist) : health(hp), distanceToPlayer(dist) {
state = std::make_unique<PatrolState>();
}
void setState(std::unique_ptr<AIState> s) {
state = std::move(s);
std::cout << "AI State: " << state->name() << '\n';
}
void update() {
state->update(*this);
}
int getHealth() const { return health; }
int getDistance() const { return distanceToPlayer; }
void takeDamage(int dmg) { health -= dmg; }
void setDistance(int dist) { distanceToPlayer = dist; }
private:
std::unique_ptr<AIState> state;
int health;
int distanceToPlayer;
};
class PatrolState : public AIState {
public:
void update(Enemy& enemy) override {
std::cout << "Patrolling...\n";
if (enemy.getDistance() < 10) {
enemy.setState(std::make_unique<ChaseState>());
}
}
std::string name() const override { return "Patrol"; }
};
class ChaseState : public AIState {
public:
void update(Enemy& enemy) override {
std::cout << "Chasing player...\n";
if (enemy.getDistance() > 20) {
enemy.setState(std::make_unique<PatrolState>());
} else if (enemy.getDistance() < 3) {
enemy.setState(std::make_unique<AttackState>());
} else if (enemy.getHealth() < 20) {
enemy.setState(std::make_unique<FleeState>());
}
}
std::string name() const override { return "Chase"; }
};
class AttackState : public AIState {
public:
void update(Enemy& enemy) override {
std::cout << "Attacking!\n";
if (enemy.getDistance() > 5) {
enemy.setState(std::make_unique<ChaseState>());
} else if (enemy.getHealth() < 20) {
enemy.setState(std::make_unique<FleeState>());
}
}
std::string name() const override { return "Attack"; }
};
class FleeState : public AIState {
public:
void update(Enemy& enemy) override {
std::cout << "Fleeing...\n";
if (enemy.getDistance() > 30) {
enemy.setState(std::make_unique<PatrolState>());
}
}
std::string name() const override { return "Flee"; }
};
int main() {
Enemy enemy(100, 15);
enemy.update(); // Patrol
enemy.setDistance(5);
enemy.update(); // Chase
enemy.setDistance(2);
enemy.update(); // Attack
enemy.takeDamage(85);
enemy.update(); // Flee
}
4. Frequently occurring problems and solutions
Problem 1: Circular dependencies
Symptom: Compilation error.
Cause: State refers to Context, Context refers to State.
// ✅ SOLVED: Forward declaration
class Context;
class State {
virtual void handle(Context& ctx) = 0;
};
Problem 2: Cost of creating a State object
Symptom: Poor performance.
Cause: State object is created for each transition.
// ✅ Solved: Flyweight (shared)
class StateManager {
static RedState redState;
static GreenState greenState;
public:
static State* getRed() { return &redState; }
static State* getGreen() { return &greenState; }
};
5. production pattern
Pattern 1: State History
class Context {
std::unique_ptr<State> state;
std::vector<std::string> history;
public:
void setState(std::unique_ptr<State> s) {
history.push_back(state->name());
state = std::move(s);
}
void printHistory() const {
for (const auto& s : history) {
std::cout << s << " -> ";
}
std::cout << state->name() << '\n';
}
};
6. Complete example: vending machine
#include <iostream>
#include <memory>
class VendingMachine;
class State {
public:
virtual void insertCoin(VendingMachine& vm) = 0;
virtual void selectProduct(VendingMachine& vm) = 0;
virtual void dispense(VendingMachine& vm) = 0;
virtual std::string name() const = 0;
virtual ~State() = default;
};
class VendingMachine {
public:
VendingMachine();
void setState(std::unique_ptr<State> s) {
state = std::move(s);
std::cout << "[State: " << state->name() << "]\n";
}
void insertCoin() { state->insertCoin(*this); }
void selectProduct() { state->selectProduct(*this); }
void dispense() { state->dispense(*this); }
private:
std::unique_ptr<State> state;
};
class NoCoinState : public State {
public:
void insertCoin(VendingMachine& vm) override;
void selectProduct(VendingMachine& vm) override {
std::cout << "Insert coin first\n";
}
void dispense(VendingMachine& vm) override {
std::cout << "Insert coin first\n";
}
std::string name() const override { return "NoCoin"; }
};
class HasCoinState : public State {
public:
void insertCoin(VendingMachine& vm) override {
std::cout << "Coin already inserted\n";
}
void selectProduct(VendingMachine& vm) override;
void dispense(VendingMachine& vm) override {
std::cout << "Select product first\n";
}
std::string name() const override { return "HasCoin"; }
};
class DispensingState : public State {
public:
void insertCoin(VendingMachine& vm) override {
std::cout << "Please wait\n";
}
void selectProduct(VendingMachine& vm) override {
std::cout << "Please wait\n";
}
void dispense(VendingMachine& vm) override;
std::string name() const override { return "Dispensing"; }
};
void NoCoinState::insertCoin(VendingMachine& vm) {
std::cout << "Coin inserted\n";
vm.setState(std::make_unique<HasCoinState>());
}
void HasCoinState::selectProduct(VendingMachine& vm) {
std::cout << "Product selected\n";
vm.setState(std::make_unique<DispensingState>());
}
void DispensingState::dispense(VendingMachine& vm) {
std::cout << "Dispensing product...\n";
vm.setState(std::make_unique<NoCoinState>());
}
VendingMachine::VendingMachine() {
state = std::make_unique<NoCoinState>();
std::cout << "[Initial state: " << state->name() << "]\n";
}
int main() {
VendingMachine vm;
vm.insertCoin();
vm.selectProduct();
vm.dispense();
}
organize
| concept | Description |
|---|---|
| State Pattern | Encapsulating state-specific behavior into classes |
| Purpose | Removal of conditional statements, clarification of state transitions |
| Structure | Context, State, ConcreteState |
| Advantages | OCP compliance, state independence, readability |
| Disadvantages | Class increases, state transition becomes more complex |
| Use Case | FSM, TCP, game AI, vending machine |
The State Pattern is a powerful pattern for implementing a state machine in an object-oriented way.
FAQ
Q1: When do I use State Pattern?
A: Used when the operation varies depending on the state and the conditional statement is complex.
Q2: What is the difference from Strategy Pattern?
A: Strategy focuses on algorithm replacement, State focuses on state transition.
Q3: What is the cost of creating a State object?
A: You can reduce costs by sharing State objects with the Flyweight pattern.
Q4: What is the status history?
A: Just record the previous state with std::vector.
Q5: What about asynchronous state transitions?
A: Asynchronous transfer is possible using std::async or an event queue.
Q6: What are the State Pattern learning resources?
A:
- “Design Patterns” by Gang of Four
- “Game Programming Patterns” by Robert Nystrom
- Refactoring Guru: State Pattern
One-line summary: State Pattern allows for a clean implementation of state machines. Next, it would be a good idea to read Decorator Pattern.
Good article to read together (internal link)
Here’s another article related to this topic.
- Complete Guide to C++ Strategy Pattern | Algorithm encapsulation and runtime replacement
- Complete Guide to C++ Command Pattern | Undo and macro system
- C++ virtual function | “Virtual Functions” guide
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented?
Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, state, pattern, fsm, state-machine, behavior, etc.
Related articles
- C++ Adapter Pattern Complete Guide | Interface conversion and compatibility
- Complete Guide to C++ Command Pattern | Undo and macro system
- C++ CRTP Complete Guide | Static polymorphism and compile-time optimization
- C++ Decorator Pattern Complete Guide | Dynamic addition and combination of functions
- C++ Default Initialization |