C++中std::variant的使用詳解和實(shí)戰(zhàn)代碼示例
std::variant 是 C++17 引入的一個(gè)類型安全的聯(lián)合體(type-safe union),它可以在多個(gè)類型之間存儲(chǔ)一個(gè)值,并在編譯時(shí)進(jìn)行類型檢查。它是現(xiàn)代 C++ 類型擦除與泛型編程的核心工具之一,適用于構(gòu)建可變類型結(jié)構(gòu)、消息傳遞系統(tǒng)、狀態(tài)機(jī)等。
一、基本概念
#include <variant> std::variant<T1, T2, ..., Tn> v;
- 類似于聯(lián)合體
union,但類型安全。 std::variant只能存儲(chǔ)其中一個(gè)類型的值。- 默認(rèn)構(gòu)造時(shí),會(huì)初始化為第一個(gè)類型的默認(rèn)值。
二、核心操作
1. 構(gòu)造與賦值
#include <iostream>
#include <variant>
int main() {
std::variant<int, std::string> v;
v = 42; // 存儲(chǔ) int
std::cout << std::get<int>(v) << std::endl;
v = "hello"; // 存儲(chǔ) string
std::cout << std::get<std::string>(v) << std::endl;
return 0;
}
2. 訪問值:std::get<T>()或std::get<index>()
std::get<int>(v); // 根據(jù)類型訪問 std::get<0>(v); // 根據(jù)索引訪問(int 是第 0 個(gè)類型)
如果類型不匹配,將拋出 std::bad_variant_access 異常。
3. 判斷當(dāng)前存儲(chǔ)的類型
if (std::holds_alternative<std::string>(v)) {
std::cout << "v contains a string\n";
}
4. 獲取當(dāng)前類型索引
std::cout << "index = " << v.index() << std::endl;
三、使用std::visit實(shí)現(xiàn)模式匹配
#include <iostream>
#include <variant>
void printVariant(const std::variant<int, std::string>& v) {
std::visit([](auto&& arg) {
std::cout << "Value: " << arg << std::endl;
}, v);
}
四、完整示例:表達(dá)式計(jì)算器
#include <iostream>
#include <variant>
#include <vector>
using Expr = std::variant<int, float, std::string>;
void processExpr(const Expr& e) {
std::visit([](auto&& val) {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int: " << val << "\n";
else if constexpr (std::is_same_v<T, float>)
std::cout << "float: " << val << "\n";
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "string: " << val << "\n";
}, e);
}
int main() {
std::vector<Expr> exprs = { 1, 3.14f, "hello" };
for (const auto& e : exprs)
processExpr(e);
return 0;
}
五、進(jìn)階用法:遞歸類型(std::monostate+std::unique_ptr)
std::variant 不能直接定義遞歸類型(自身包含自身)。需要使用指針 + std::monostate:
struct Node;
using Tree = std::variant<
std::monostate, // 空節(jié)點(diǎn)
int, // 葉子
std::unique_ptr<Node> // 子樹
>;
struct Node {
Tree left;
Tree right;
};
六、注意事項(xiàng)
| 項(xiàng)目 | 說明 |
|---|---|
| 異常 | std::get<T>(v) 如果類型不對(duì),會(huì)拋出 std::bad_variant_access |
| 默認(rèn)值 | 默認(rèn)初始化為第一個(gè)類型的默認(rèn)構(gòu)造值 |
| 索引 | v.index() 返回當(dāng)前類型在 variant 中的索引 |
| 編譯期匹配 | 使用 if constexpr + std::visit 可實(shí)現(xiàn)類型安全分派 |
七、綜合實(shí)戰(zhàn)示例
下面是一個(gè)用 std::variant 實(shí)現(xiàn)的 有限狀態(tài)機(jī)(FSM)+ 消息分發(fā)系統(tǒng) 的綜合示例,適用于狀態(tài)驅(qū)動(dòng)的系統(tǒng),如游戲角色狀態(tài)、UI 狀態(tài)、網(wǎng)絡(luò)連接狀態(tài)等。
示例目的結(jié)構(gòu)
我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 FSM:角色可以在 Idle, Moving, Attacking 三個(gè)狀態(tài)間切換,接收 Message 控制狀態(tài)轉(zhuǎn)換。
1. 基礎(chǔ)定義
#include <iostream>
#include <variant>
#include <string>
// 狀態(tài)定義
struct Idle {};
struct Moving { std::string destination; };
struct Attacking { std::string target; };
// 消息定義
struct StartMoving { std::string destination; };
struct StopMoving {};
struct StartAttack { std::string target; };
struct StopAttack {};
// 狀態(tài)類型和消息類型
using State = std::variant<Idle, Moving, Attacking>;
using Message = std::variant<StartMoving, StopMoving, StartAttack, StopAttack>;
2. 消息處理器(核心)
void handle_message(State& state, const Message& msg) {
std::visit([&](auto&& current_state) {
using Current = std::decay_t<decltype(current_state)>;
std::visit([&](auto&& m) {
using Msg = std::decay_t<decltype(m)>;
if constexpr (std::is_same_v<Current, Idle>) {
if constexpr (std::is_same_v<Msg, StartMoving>) {
std::cout << "Idle → Moving to " << m.destination << "\n";
state = Moving{m.destination};
} else if constexpr (std::is_same_v<Msg, StartAttack>) {
std::cout << "Idle → Attacking " << m.target << "\n";
state = Attacking{m.target};
}
} else if constexpr (std::is_same_v<Current, Moving>) {
if constexpr (std::is_same_v<Msg, StopMoving>) {
std::cout << "Moving → Idle\n";
state = Idle{};
} else if constexpr (std::is_same_v<Msg, StartAttack>) {
std::cout << "Moving → Attacking " << m.target << "\n";
state = Attacking{m.target};
}
} else if constexpr (std::is_same_v<Current, Attacking>) {
if constexpr (std::is_same_v<Msg, StopAttack>) {
std::cout << "Attacking → Idle\n";
state = Idle{};
} else if constexpr (std::is_same_v<Msg, StartMoving>) {
std::cout << "Attacking → Moving to " << m.destination << "\n";
state = Moving{m.destination};
}
}
}, msg);
}, state);
}
3. 主程序(狀態(tài)切換測(cè)試)
int main() {
State state = Idle{};
handle_message(state, StartMoving{"North"});
handle_message(state, StartAttack{"Dragon"});
handle_message(state, StopAttack{});
handle_message(state, StartAttack{"Goblin"});
handle_message(state, StartMoving{"East"});
handle_message(state, StopMoving{});
return 0;
}
輸出示例:
Idle → Moving to North
Moving → Attacking Dragon
Attacking → Idle
Idle → Attacking Goblin
Attacking → Moving to East
Moving → Idle
小結(jié):此結(jié)構(gòu)的優(yōu)勢(shì)
| 特性 | 說明 |
|---|---|
| 類型安全 | std::variant 和 std::visit 編譯期確定類型,無需 RTTI 或類型轉(zhuǎn)換 |
| 易擴(kuò)展 | 增加新狀態(tài)或消息只需添加結(jié)構(gòu)體 + 分支處理 |
| 面向?qū)ο罂商娲?/td> | 可替代經(jīng)典面向?qū)ο鬆顟B(tài)機(jī)中的虛函數(shù)分派 |
| 適合嵌入式/游戲 | 無動(dòng)態(tài)分配,運(yùn)行時(shí)高效 |
總結(jié)
| 用法 | 說明 |
|---|---|
| std::variant<A, B> | 表示可以存儲(chǔ) A 或 B 類型的變量 |
| std::get<T>(v) | 獲取當(dāng)前存儲(chǔ)的值(類型匹配) |
| std::visit() | 模式匹配:對(duì)當(dāng)前值執(zhí)行對(duì)應(yīng)操作 |
| holds_alternative<T>() | 判斷當(dāng)前是否存儲(chǔ)的是 T 類型 |
到此這篇關(guān)于C++中std::variant的使用詳解和實(shí)戰(zhàn)代碼示例的文章就介紹到這了,更多相關(guān)C++ std::variant使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VC中LINK 2001 和 LINK 2009 的錯(cuò)誤的解決方法
最近將兩個(gè)開源C++項(xiàng)目編譯成windows版本的時(shí)候遇到很多問題,編譯的時(shí)候總是報(bào)錯(cuò),報(bào)的最多的是無法解析的外部符號(hào)”,經(jīng)過近3天的折騰總算都通過了,這里是一些總結(jié)2020-10-10
C語(yǔ)言用棧模擬實(shí)現(xiàn)隊(duì)列問題詳解
本片文章帶你分析如何用兩個(gè)棧,并且只使用棧的基本功能來模擬實(shí)現(xiàn)隊(duì)列,其中同樣只實(shí)現(xiàn)隊(duì)列的基本功能,感興趣的朋友來看看吧2022-04-04
使用C++的inipp庫(kù)處理配置文件.ini的示例詳解
一個(gè)ini文件由多個(gè)節(jié)section組成,每個(gè)節(jié)由多個(gè)鍵值對(duì)組成,本文給大家介紹了使用第三方庫(kù)inipp來操作ini文件,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-01-01
DSP中浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算--定點(diǎn)數(shù)的加減乘除運(yùn)算
本文主要介紹DSP中定點(diǎn)數(shù)的加減乘除運(yùn)算,很值得學(xué)習(xí)一下,需要的朋友可以參考一下。2016-06-06
OpenCV獲取鼠標(biāo)左鍵點(diǎn)擊位置圖像的像素值
這篇文章主要為大家詳細(xì)介紹了OpenCV獲取鼠標(biāo)左鍵點(diǎn)擊位置圖像的像素值,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
深入解析C++編程中基類與基類的繼承的相關(guān)知識(shí)
這篇文章主要介紹了C++編程中基類與基類的繼承的相關(guān)知識(shí),包括多個(gè)基類繼承與虛擬基類等重要知識(shí),需要的朋友可以參考下2016-01-01

