C++實(shí)現(xiàn)狀態(tài)機(jī)的四種方法
更新時(shí)間:2026年06月02日 09:21:32 作者:筠筠喵嗚喵
本文主要介紹了狀態(tài)機(jī)實(shí)現(xiàn)的四種方法,包括Switch、stdstd::variant&std::visit、查表法和狀態(tài)模式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
1.Switch
- 優(yōu)點(diǎn):直觀、零額外抽象、性能最好
- 缺點(diǎn):case 越多越臃腫,邏輯分散。
#include <iostream>
enum class State { Idle, Running, Paused, Error };
enum class Event { Start, Pause, Resume, Stop, ErrorOccur, Reset };
struct SM {
State s = State::Idle;
void dispatch(Event ev) {
switch (s) {
case State::Idle:
if (ev == Event::Start) { enter(State::Running); }
else if (ev == Event::ErrorOccur) { enter(State::Error); }
break;
case State::Running:
if (ev == Event::Pause) { enter(State::Paused); }
else if (ev == Event::Stop) { enter(State::Idle); }
else if (ev == Event::ErrorOccur) { enter(State::Error); }
break;
case State::Paused:
if (ev == Event::Resume) { enter(State::Running); }
else if (ev == Event::Stop) { enter(State::Idle); }
else if (ev == Event::ErrorOccur) { enter(State::Error); }
break;
case State::Error:
if (ev == Event::Reset) { enter(State::Idle); }
break;
}
}
void enter(State ns) {
onExit(s);
s = ns;
onEnter(s);
}
void onEnter(State st) {
std::cout << "enter " << toString(st) << "\n";
}
void onExit(State st) {
std::cout << "exit " << toString(st) << "\n";
}
static const char* toString(State st) {
switch (st) {
case State::Idle: return "Idle";
case State::Running: return "Running";
case State::Paused: return "Paused";
case State::Error: return "Error";
}
return "Unknown";
}
};
int main() {
SM sm;
sm.dispatch(Event::Start);
sm.dispatch(Event::Pause);
sm.dispatch(Event::Resume);
sm.dispatch(Event::ErrorOccur);
sm.dispatch(Event::Reset);
return 0;
}
2. std::variant + std::visit
- 優(yōu)點(diǎn):每個(gè)狀態(tài)行為封裝成類型,類型安全且可內(nèi)聯(lián)(無虛調(diào)用)。
- 缺點(diǎn):增加類型數(shù)量,添加新狀態(tài)需改 variant 列表和工廠。
#include <iostream>
#include <variant>
#include <memory>
struct Context;
// 各狀態(tài)類型封裝行為
struct Idle {
static void onEnter(Context&);
static void onExit(Context&);
static void handle(Context&, int ev);
static constexpr const char* name = "Idle";
};
struct Running {
static void onEnter(Context&);
static void onExit(Context&);
static void handle(Context&, int ev);
static constexpr const char* name = "Running";
};
struct Paused {
static void onEnter(Context&);
static void onExit(Context&);
static void handle(Context&, int ev);
static constexpr const char* name = "Paused";
};
struct ErrorSt {
static void onEnter(Context&);
static void onExit(Context&);
static void handle(Context&, int ev);
static constexpr const char* name = "Error";
};
using StateVar = std::variant<Idle, Running, Paused, ErrorSt>;
struct Context {
StateVar state{Idle{}};
void dispatch(int ev) {
std::visit([&](auto &st){
using T = std::decay_t<decltype(st)>;
T::handle(*this, ev);
}, state);
}
void setState(StateVar ns) {
std::visit([&](auto &st){ using T = std::decay_t<decltype(st)>; T::onExit(*this); }, state);
state = std::move(ns);
std::visit([&](auto &st){ using T = std::decay_t<decltype(st)>; T::onEnter(*this); }, state);
}
void log(const char* msg) { std::cout << msg << "\n"; }
};
void Idle::onEnter(Context& c){ c.log("enter Idle"); }
void Idle::onExit(Context& c){ c.log("exit Idle"); }
void Idle::handle(Context& c, int ev){
if(ev==1) c.setState(Running{});
else if(ev==99) c.setState(ErrorSt{});
else c.log("Idle ignore");
}
void Running::onEnter(Context& c){ c.log("enter Running"); }
void Running::onExit(Context& c){ c.log("exit Running"); }
void Running::handle(Context& c, int ev){
if(ev==2) c.setState(Paused{});
else if(ev==0) c.setState(Idle{});
else if(ev==99) c.setState(ErrorSt{});
else c.log("Running ignore");
}
void Paused::onEnter(Context& c){ c.log("enter Paused"); }
void Paused::onExit(Context& c){ c.log("exit Paused"); }
void Paused::handle(Context& c, int ev){
if(ev==3) c.setState(Running{});
else if(ev==0) c.setState(Idle{});
else if(ev==99) c.setState(ErrorSt{});
else c.log("Paused ignore");
}
void ErrorSt::onEnter(Context& c){ c.log("enter Error"); }
void ErrorSt::onExit(Context& c){ c.log("exit Error"); }
void ErrorSt::handle(Context& c, int ev){
if(ev==4) c.setState(Idle{});
else c.log("Error ignore");
}
int main(){
Context ctx;
ctx.dispatch(1); // Start -> Running
ctx.dispatch(2); // Pause -> Paused
ctx.dispatch(3); // Resume -> Running
ctx.dispatch(99); // Error -> Error
ctx.dispatch(4); // Reset -> Idle
return 0;
}
3.查表法
- 優(yōu)點(diǎn):配置化、數(shù)據(jù)驅(qū)動、容易以表格/文件維護(hù)和測試,性能極高。
- 缺點(diǎn):運(yùn)行時(shí)查表開銷、復(fù)雜行為(guards/enter/exit)仍需回調(diào)。動作(action)與狀態(tài)分離,擴(kuò)展性較差
#include <iostream>
#include <map>
#include <functional>
#include <tuple>
enum class State { Idle, Running, Paused, Error };
enum class Event { Start, Pause, Resume, Stop, ErrorOccur, Reset };
// 表項(xiàng):(State,Event) -> (new State, action)
using Key = std::pair<State, Event>;
struct Action { State next; std::function<void()> act; };
struct SM {
std::map<Key, Action> table;
State s = State::Idle;
SM() {
table[{State::Idle, Event::Start}] = {State::Running, [this](){ log("Idle->Running"); }};
table[{State::Idle, Event::ErrorOccur}] = {State::Error, [this](){ log("Idle->Error"); }};
table[{State::Running, Event::Pause}] = {State::Paused, [this](){ log("Running->Paused"); }};
table[{State::Running, Event::Stop}] = {State::Idle, [this](){ log("Running->Idle"); }};
table[{State::Running, Event::ErrorOccur}] = {State::Error, [this](){ log("Running->Error"); }};
table[{State::Paused, Event::Resume}] = {State::Running, [this](){ log("Paused->Running"); }};
table[{State::Paused, Event::Stop}] = {State::Idle, [this](){ log("Paused->Idle"); }};
table[{State::Paused, Event::ErrorOccur}] = {State::Error, [this](){ log("Paused->Error"); }};
table[{State::Error, Event::Reset}] = {State::Idle, [this](){ log("Error->Idle"); }};
}
void dispatch(Event ev) {
auto it = table.find({s, ev});
if (it != table.end()) {
onExit(s);
it->second.act();
s = it->second.next;
onEnter(s);
} else {
log("no transition");
}
}
void onEnter(State st){ std::cout << "enter " << name(st) << "\n"; }
void onExit(State st){ std::cout << "exit " << name(st) << "\n"; }
void log(const char* m){ std::cout << m << "\n"; }
static const char* name(State st){
switch(st){ case State::Idle: return "Idle"; case State::Running: return "Running"; case State::Paused: return "Paused"; case State::Error: return "Error"; }
return "Unknown";
}
};
int main(){
SM sm;
sm.dispatch(Event::Start);
sm.dispatch(Event::Pause);
sm.dispatch(Event::Resume);
sm.dispatch(Event::ErrorOccur);
sm.dispatch(Event::Reset);
return 0;
}
4.狀態(tài)模式(虛函數(shù)實(shí)現(xiàn))
- 優(yōu)點(diǎn):面向?qū)ο?、可在狀態(tài)間封裝復(fù)雜 enter/exit 行為,運(yùn)行時(shí)靈活。
- 缺點(diǎn):類數(shù)量與抽象開銷(虛函數(shù)/堆分配),需謹(jǐn)慎優(yōu)化(對象復(fù)用或單例狀態(tài)可減開銷)。
#include <iostream>
#include <memory>
#include <string>
enum class Event { Start, Pause, Resume, Stop, ErrorOccur, Reset };
class Context;
struct State {
virtual ~State() = default;
virtual void onEnter(Context&) {}
virtual void onExit(Context&) {}
virtual void handle(Context&, Event) = 0;
virtual std::string name() const = 0;
};
class Context {
public:
void setState(std::unique_ptr<State> s) {
if (state) state->onExit(*this);
state = std::move(s);
if (state) state->onEnter(*this);
}
void handle(Event ev) {
if (state) state->handle(*this, ev);
}
void log(const std::string &m) { std::cout << m << "\n"; }
private:
std::unique_ptr<State> state;
};
// Concrete states
struct Idle : State {
void onEnter(Context& c) override { c.log("enter Idle"); }
void onExit(Context& c) override { c.log("exit Idle"); }
void handle(Context& c, Event ev) override;
std::string name() const override { return "Idle"; }
};
struct Running : State {
void onEnter(Context& c) override { c.log("enter Running"); }
void onExit(Context& c) override { c.log("exit Running"); }
void handle(Context& c, Event ev) override;
std::string name() const override { return "Running"; }
};
struct Paused : State {
void onEnter(Context& c) override { c.log("enter Paused"); }
void onExit(Context& c) override { c.log("exit Paused"); }
void handle(Context& c, Event ev) override;
std::string name() const override { return "Paused"; }
};
struct ErrorState : State {
void onEnter(Context& c) override { c.log("enter Error"); }
void onExit(Context& c) override { c.log("exit Error"); }
void handle(Context& c, Event ev) override;
std::string name() const override { return "Error"; }
};
void Idle::handle(Context& c, Event ev){
if(ev==Event::Start) c.setState(std::make_unique<Running>());
else if(ev==Event::ErrorOccur) c.setState(std::make_unique<ErrorState>());
else c.log("Idle ignore");
}
void Running::handle(Context& c, Event ev){
if(ev==Event::Pause) c.setState(std::make_unique<Paused>());
else if(ev==Event::Stop) c.setState(std::make_unique<Idle>());
else if(ev==Event::ErrorOccur) c.setState(std::make_unique<ErrorState>());
else c.log("Running ignore");
}
void Paused::handle(Context& c, Event ev){
if(ev==Event::Resume) c.setState(std::make_unique<Running>());
else if(ev==Event::Stop) c.setState(std::make_unique<Idle>());
else if(ev==Event::ErrorOccur) c.setState(std::make_unique<ErrorState>());
else c.log("Paused ignore");
}
void ErrorState::handle(Context& c, Event ev){
if(ev==Event::Reset) c.setState(std::make_unique<Idle>());
else c.log("Error ignore");
}
int main(){
Context ctx;
ctx.setState(std::make_unique<Idle>());
ctx.handle(Event::Start);
ctx.handle(Event::Pause);
ctx.handle(Event::Resume);
ctx.handle(Event::ErrorOccur);
ctx.handle(Event::Reset);
return 0;
}
5. 方案對比總結(jié)
| 方案 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
|---|---|---|---|
| switch | 1. 最簡單直觀 2. 零額外抽象,性能最好(編譯期確定) 3. 代碼集中,便于理解 | 1. 狀態(tài)/事件增多時(shí),switch-case 臃腫 2. 邏輯分散,可維護(hù)性差 3. 難以擴(kuò)展(添加新狀態(tài)需修改多處) | 狀態(tài)機(jī)規(guī)模?。?lt;10 個(gè)狀態(tài)),性能要求極高,且不預(yù)期頻繁變更的場景 |
| std::variant + std::visit | 1. 類型安全,每個(gè)狀態(tài)行為封裝為獨(dú)立類型 2. 無虛函數(shù)調(diào)用,可內(nèi)聯(lián)優(yōu)化 3. 編譯期檢查,避免遺漏狀態(tài)處理 | 1. 類型數(shù)量增加,代碼量較大 2. 添加新狀態(tài)需修改 variant 列表和工廠 3. 對 C++17 及以上版本有要求 | 狀態(tài)集固定,希望將行為與類型綁定,且追求高性能、類型安全的場景 |
| 查表法 | 1. 高度配置化、數(shù)據(jù)驅(qū)動 2. 易于維護(hù)和測試(表格/文件可外部化) 3. 性能極高(O(1) 查表) 4. 狀態(tài)轉(zhuǎn)換邏輯集中 | 1. 運(yùn)行時(shí)查表有輕微開銷 2. 復(fù)雜行為(guard 條件、enter/exit)仍需回調(diào)函數(shù) 3. 動作與狀態(tài)分離,擴(kuò)展性較差 | 狀態(tài)轉(zhuǎn)換規(guī)則穩(wěn)定、希望外部配置、便于測試和動態(tài)調(diào)整的場景 |
| 狀態(tài)模式(虛函數(shù)) | 1. 面向?qū)ο?,符合開閉原則 2. 易于管理復(fù)雜的 enter/exit 行為 3. 運(yùn)行時(shí)靈活,狀態(tài)可動態(tài)替換 4. 結(jié)構(gòu)清晰,職責(zé)分離 | 1. 虛函數(shù)調(diào)用開銷(運(yùn)行時(shí)多態(tài)) 2. 類數(shù)量多,可能涉及堆分配 3. 若狀態(tài)對象非單例,會有對象創(chuàng)建開銷 | 狀態(tài)行為復(fù)雜、需要封裝大量狀態(tài)專屬邏輯、且預(yù)期會頻繁擴(kuò)展的場景 |
到此這篇關(guān)于C++實(shí)現(xiàn)狀態(tài)機(jī)的四種方法的文章就介紹到這了,更多相關(guān)C++ 狀態(tài)機(jī)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析C語言中printf(),sprintf(),scanf(),sscanf()的用法和區(qū)別
以下是對C語言中printf(),sprintf(),scanf(),sscanf()的用法以及區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以參考下2013-07-07
C++使用string的大數(shù)取模運(yùn)算(5)
這篇文章主要為大家詳細(xì)介紹了C++使用string的大數(shù)取模運(yùn)算,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
C語言詳解關(guān)鍵字sizeof與unsigned及signed的用法
這篇文章主要為大家詳細(xì)介紹了C語言關(guān)鍵字sizeof&&unsigned&&signed,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

