C++ State Pattern: Complete Guide | Finite State Machines & Behavior

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

  1. Basic structure
  2. State transition diagram
  3. Game AI example
  4. Frequently occurring problems and solutions
  5. Production Patterns
  6. 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

conceptDescription
State PatternEncapsulating state-specific behavior into classes
PurposeRemoval of conditional statements, clarification of state transitions
StructureContext, State, ConcreteState
AdvantagesOCP compliance, state independence, readability
DisadvantagesClass increases, state transition becomes more complex
Use CaseFSM, 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:

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.


  • 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 |