在现代C++中,std::variant 提供了一种类型安全的方式来保存多种可能的值,类似于联合体,但具有更强的类型检查和更易用的接口。利用 std::variant 可以轻松实现一个解析器,能够将输入字符串解析为不同的结构体类型,并保证在处理过程中不会出现类型错误。下面给出一个完整的示例,演示如何使用 std::variant、std::visit、以及结构体模板化来构建一个简易的多态解析器。
1. 定义多种数据结构
#include <iostream>
#include <variant>
#include <string>
#include <sstream>
#include <iomanip>
#include <vector>
struct User {
std::string name;
int age;
};
struct Product {
std::string name;
double price;
};
struct Order {
std::string id;
std::vector<std::string> items;
};
2. 为每种结构体实现解析函数
bool parseUser(const std::string& line, User& out) {
std::istringstream ss(line);
return (ss >> out.name >> out.age) && ss.eof();
}
bool parseProduct(const std::string& line, Product& out) {
std::istringstream ss(line);
return (ss >> out.name >> out.price) && ss.eof();
}
bool parseOrder(const std::string& line, Order& out) {
std::istringstream ss(line);
std::string item;
if (!(ss >> out.id)) return false;
while (ss >> item) out.items.push_back(item);
return !out.items.empty();
}
3. 定义 std::variant 以及 std::visit 访问器
using Item = std::variant<User, Product, Order>;
struct ItemPrinter {
void operator()(const User& u) const {
std::cout << std::left << std::setw(10) << "User" << " | Name: " << u.name << ", Age: " << u.age << '\n';
}
void operator()(const Product& p) const {
std::cout << std::left << std::setw(10) << "Product" << " | Name: " << p.name << ", Price: $" << std::fixed << std::setprecision(2) << p.price << '\n';
}
void operator()(const Order& o) const {
std::cout << std::left << std::setw(10) << "Order" << " | ID: " << o.id << ", Items: ";
for (size_t i = 0; i < o.items.size(); ++i) {
std::cout << o.items[i];
if (i + 1 < o.items.size()) std::cout << ", ";
}
std::cout << '\n';
}
};
4. 解析并处理多行输入
Item parseLine(const std::string& line) {
User u;
if (parseUser(line, u)) return u;
Product p;
if (parseProduct(line, p)) return p;
Order o;
if (parseOrder(line, o)) return o;
throw std::runtime_error("未知的数据格式: " + line);
}
5. 主程序
int main() {
std::vector<std::string> inputs = {
"Alice 28",
"Widget 19.99",
"ORD123 ItemA ItemB ItemC",
"Bob 35",
"Gadget 49.5",
"ORD124 ItemX ItemY"
};
std::vector <Item> items;
for (const auto& line : inputs) {
try {
items.push_back(parseLine(line));
} catch (const std::exception& e) {
std::cerr << "解析错误: " << e.what() << '\n';
}
}
std::cout << "=== 解析结果 ===\n";
for (const auto& item : items) {
std::visit(ItemPrinter{}, item);
}
return 0;
}
6. 运行结果
=== 解析结果 ===
User | Name: Alice, Age: 28
Product | Name: Widget, Price: $19.99
Order | ID: ORD123, Items: ItemA, ItemB, ItemC
User | Name: Bob, Age: 35
Product | Name: Gadget, Price: $49.50
Order | ID: ORD124, Items: ItemX, ItemY
7. 代码说明
- 类型安全:
std::variant确保每个Item必须是User、Product或Order之一,编译器会在访问时检查类型,避免了传统void*的危险。 - 简洁的访问:
std::visit与ItemPrinter结合,实现了对不同类型的统一打印逻辑,代码可读性大幅提升。 - 易于扩展:只需添加新的结构体、解析函数以及对应的访问器实现,整体框架不需要改动。
- 错误处理:如果一行无法匹配任何已知格式,
parseLine抛出异常,主程序捕获并报告错误,保证程序不会在解析失败时崩溃。
8. 进阶改进
- **使用 `std::optional `** 代替抛异常,在解析失败时返回空值,更符合现代 C++ 的异常友好设计。
- 正则表达式:为复杂数据结构使用正则表达式提取字段,进一步提升解析灵活性。
- 自定义访问器:利用
std::variant的visit支持多重重载,直接在ItemPrinter中实现多态打印逻辑,而无需显式if-else或switch。
通过上述示例,你可以快速构建一个类型安全且易维护的多态结构体解析器。std::variant 与 std::visit 的组合,使得 C++ 代码既保持了强类型优势,又拥有了类似脚本语言的数据处理灵活性。