C++中如何高效存储并检索类/结构内的多类型Token值?
问题背景
我正在用C++开发一个基础语言解释器,用于学习和实践。需要存储词法分析得到的Token,且Token需能存储对应的值(如int、string、float字面量),同时要能以最少代码高效检索这些值。
最初我仅存储Token的文本,在需要时由解释器转换为int或float,但发现这种方式效率极低(如循环中大量字符串转换场景)。之后我采用了基类+子类的方案:
class Token { public: Type m_type; std::string m_text; Token(const std::string& text, const Type& type) : m_text(text), m_type(type) {} virtual void getValue(int&) = 0; virtual void getValue(double&) = 0; virtual void getValue(std::string&) = 0; virtual ~Token() {} }; class IntToken : public Token { public: int m_data; IntToken(const std::string& text, const Type& type, const int& data) : Token(text, type), m_data(data) {} void getValue(int& val) { val = m_data; } void getValue(double& val) {} void getValue(std::string&) {} };
其余子类与IntToken类似,仅存储的数据类型不同,通过std::vector<Token*> tokens;来存储所有Token。但问题在于每次检索值时都需要判断Token类型,代码中会出现大量switch语句,检索操作非常繁琐。
解决方案
方案一:使用std::variant(C++17及以上)
std::variant是C++17引入的类型安全联合体,可直接存储多种类型的值,无需维护继承体系,代码简洁且类型安全。
实现示例:
#include <variant> #include <string> enum class TokenType { Int, Float, String, Identifier // 可扩展其他Token类型 }; struct Token { TokenType type; std::string text; std::variant<int, double, std::string> value; }; // 存储Token的容器 std::vector<Token> tokens;
检索值时用std::visit处理不同类型,避免switch:
// 示例:遍历处理Token值 void process_int(int val) { /* 处理int逻辑 */ } void process_double(double val) { /* 处理double逻辑 */ } void process_string(const std::string& val) { /* 处理string逻辑 */ } for (const auto& token : tokens) { std::visit([&](auto&& val) { using T = std::decay_t<decltype(val)>; if constexpr (std::is_same_v<T, int>) { process_int(val); } else if constexpr (std::is_same_v<T, double>) { process_double(val); } else if constexpr (std::is_same_v<T, std::string>) { process_string(val); } }, token.value); }
优势:
- 无需继承体系,代码量大幅减少
- 编译期类型检查,避免运行时转换错误
- 内存效率高,仅占用最大类型的内存+少量额外开销
方案二:访问者模式(兼容C++17以下版本)
若需保留继承体系,可通过访问者模式消除switch语句,将类型判断逻辑封装到访问者类中。
首先修改Token基类,添加accept方法:
class TokenVisitor; class Token { public: Type m_type; std::string m_text; Token(const std::string& text, const Type& type) : m_text(text), m_type(type) {} virtual void accept(TokenVisitor& visitor) = 0; virtual ~Token() {} }; // 定义访问者基类 class TokenVisitor { public: virtual void visit(IntToken& token) = 0; virtual void visit(FloatToken& token) = 0; virtual void visit(StringToken& token) = 0; // 新增Token类型时添加对应visit方法 };
子类实现accept方法:
class IntToken : public Token { public: int m_data; IntToken(const std::string& text, const Type& type, const int& data) : Token(text, type), m_data(data) {} void accept(TokenVisitor& visitor) override { visitor.visit(*this); } }; // FloatToken、StringToken同理实现accept方法
使用时创建具体访问者处理逻辑:
class ValueProcessor : public TokenVisitor { public: void visit(IntToken& token) override { process_int(token.m_data); } void visit(FloatToken& token) override { process_float(token.m_data); } void visit(StringToken& token) override { process_string(token.m_data); } }; // 使用示例 ValueProcessor processor; for (auto* token : tokens) { token->accept(processor); }
优势:
- 消除冗余switch语句,符合开闭原则,新增Token类型只需扩展访问者方法
- 逻辑集中在访问者类中,代码结构更清晰
方案三:std::any(C++17及以上)
std::any可存储任意类型,但需运行时类型检查,类型安全性弱于std::variant,适合类型不确定的场景,解释器Token场景下优先级低于前两种方案。
示例:
#include <any> #include <string> struct Token { TokenType type; std::string text; std::any value; }; // 检索值示例 if (token.type == TokenType::Int) { int val = std::any_cast<int>(token.value); process_int(val); }
总结
若使用C++17及以上版本,std::variant是最优选择,兼顾简洁性、类型安全与效率;若需保留继承体系,访问者模式可有效消除switch语句,提升代码可维护性。
内容的提问来源于stack exchange,提问作者Tiberiu Popescu

