[2026] C++ State Pattern 완벽 가이드 | 상태 기계와 동작 캡슐화

[2026] C++ State Pattern 완벽 가이드 | 상태 기계와 동작 캡슐화

이 글의 핵심

C++ State Pattern : 상태 기계와 동작 캡슐화. State Pattern이란?. 왜 필요한가·기본 구조.

State Pattern이란? 왜 필요한가

상태 전이를 객체로 나누는 내용은 행동 패턴 시리즈·Strategy 글과 대비하면 이해가 빨라집니다.

문제 시나리오: 조건문 폭발

문제: 객체의 상태에 따라 동작이 달라지면, 거대한 if-else가 생깁니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 나쁜 예: 조건문 폭발
class TCPConnection {
    enum State { CLOSED, LISTEN, ESTABLISHED };
    State state = CLOSED;
    
    void open() {
        if (state == CLOSED) {
            // LISTEN으로 전이
        } else if (state == LISTEN) {
            // 이미 열림
        } else if (state == ESTABLISHED) {
            // 에러
        }
    }
    
    void close() {
        if (state == CLOSED) {
            // 에러
        } else if (state == LISTEN) {
            // CLOSED로 전이
        } else if (state == ESTABLISHED) {
            // CLOSED로 전이
        }
    }
};

해결: State Pattern각 상태를 클래스로 캡슐화합니다. Context는 현재 State 객체를 가지고, 요청을 State에 위임합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 좋은 예: 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 {
        // 에러
    }
};
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); }
};

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

stateDiagram-v2
    [*] --> Closed
    Closed --> Listen: open()
    Listen --> Established: accept()
    Established --> Closed: close()
    Listen --> Closed: close()

목차

  1. 기본 구조
  2. 상태 전이 다이어그램
  3. 게임 AI 예제
  4. 자주 발생하는 문제와 해결법
  5. 프로덕션 패턴
  6. 완전한 예제: 자판기

1. 기본 구조

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 상태 전이 다이어그램

TCP 연결 상태

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 게임 AI 예제

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 자주 발생하는 문제와 해결법

문제 1: 순환 의존성

증상: 컴파일 에러. 원인: State가 Context를 참조, Context가 State를 참조. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ 해결: 전방 선언
class Context;
class State {
    virtual void handle(Context& ctx) = 0;
};

문제 2: State 객체 생성 비용

증상: 성능 저하. 원인: 전이마다 State 객체 생성. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ 해결: Flyweight (공유)
class StateManager {
    static RedState redState;
    static GreenState greenState;
public:
    static State* getRed() { return &redState; }
    static State* getGreen() { return &greenState; }
};

5. 프로덕션 패턴

패턴 1: 상태 히스토리

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 완전한 예제: 자판기

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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();
}

정리

개념설명
State Pattern상태별 동작을 클래스로 캡슐화
목적조건문 제거, 상태 전이 명확화
구조Context, State, ConcreteState
장점OCP 준수, 상태 독립성, 가독성
단점클래스 증가, 상태 전이 복잡
사용 사례FSM, TCP, 게임 AI, 자판기
State Pattern은 상태 기계를 객체 지향적으로 구현하는 강력한 패턴입니다.

FAQ

Q1: State Pattern은 언제 쓰나요?

A: 상태에 따라 동작이 달라지고, 조건문이 복잡할 때 사용합니다.

Q2: Strategy Pattern과 차이는?

A: Strategy알고리즘 교체, State상태 전이에 집중합니다.

Q3: State 객체 생성 비용은?

A: Flyweight 패턴으로 State 객체를 공유하면 비용을 줄일 수 있습니다.

Q4: 상태 히스토리는?

A: std::vector로 이전 상태를 기록하면 됩니다.

Q5: 비동기 상태 전이는?

A: std::async나 이벤트 큐로 비동기 전이 가능합니다.

Q6: State Pattern 학습 리소스는?

A:

  • “Design Patterns” by Gang of Four
  • “Game Programming Patterns” by Robert Nystrom
  • Refactoring Guru: State Pattern 한 줄 요약: State Pattern으로 상태 기계를 깔끔하게 구현할 수 있습니다. 다음으로 Decorator Pattern을 읽어보면 좋습니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3