《Crafting an Interpreter》C++实现:创建多类型Token对象向量的方法
实现多类型字面量Token的几种方案
方案1:使用std::variant(C++17及以上推荐)
这是现代C++里最简洁、类型安全的解决方案,std::variant本质是类型安全的联合,可以存储指定范围内的任意一种类型,完美替代Java的Object来存储不同字面量。
代码示例
首先定义Token类型和字面量的variant类型:
#include <variant> #include <string> #include <vector> enum class TokenType { String, Number, Identifier, // 补充其他Token类型(如关键字、符号) }; // 定义字面量可能的类型,std::monostate用于无字面量的Token using Literal = std::variant<std::monostate, std::string, double>; struct Token { TokenType type; std::string lexeme; Literal literal; int line; Token(TokenType t, std::string lex, Literal lit, int ln) : type(t), lexeme(std::move(lex)), literal(std::move(lit)), line(ln) {} }; // 直接将不同类型的Token存入同一vector std::vector<Token> tokens; tokens.emplace_back(TokenType::String, "\"hello\"", std::string("hello"), 1); tokens.emplace_back(TokenType::Number, "123", 123.0, 2);
访问字面量
用std::visit处理不同类型的字面量:
#include <iostream> #include <functional> void printLiteral(const Literal& lit) { std::visit(std::overloaded{ [](std::monostate) { std::cout << "无字面量"; }, [](const std::string& s) { std::cout << "字符串: " << s; }, [](double n) { std::cout << "数字: " << n; } }, lit); } // 使用示例 for (const auto& token : tokens) { std::cout << "Token类型: " << static_cast<int>(token.type) << ", 字面量: "; printLiteral(token.literal); std::cout << "\n"; }
优点:类型安全、无需手动管理内存、代码简洁,是现代C++的首选方案。
方案2:多态+智能指针(兼容C++11及以上)
如果需要兼容更早的C++标准,可以用多态实现。注意不能直接存储抽象类对象在vector里,需存储智能指针避免内存泄漏。
代码示例
首先定义抽象基类IToken:
#include <memory> #include <vector> #include <string> enum class TokenType { String, Number, Identifier }; class IToken { public: TokenType type; std::string lexeme; int line; IToken(TokenType t, std::string lex, int ln) : type(t), lexeme(std::move(lex)), line(ln) {} virtual ~IToken() = default; // 虚析构确保子类正确析构 // 纯虚函数,用于获取字面量的字符串表示(可按需扩展接口) virtual std::string getLiteralString() const = 0; }; // 字符串Token子类 class StringToken : public IToken { public: std::string literal; StringToken(std::string lex, std::string lit, int ln) : IToken(TokenType::String, std::move(lex), ln), literal(std::move(lit)) {} std::string getLiteralString() const override { return literal; } }; // 数字Token子类 class NumberToken : public IToken { public: double literal; NumberToken(std::string lex, double lit, int ln) : IToken(TokenType::Number, std::move(lex), ln), literal(lit) {} std::string getLiteralString() const override { return std::to_string(literal); } }; // 用智能指针存储Token std::vector<std::unique_ptr<IToken>> tokens; tokens.push_back(std::make_unique<StringToken>("\"hello\"", "hello", 1)); tokens.push_back(std::make_unique<NumberToken>("123", 123.0, 2));
访问字面量
通过多态接口或动态类型转换:
for (const auto& token : tokens) { std::cout << "Token类型: " << static_cast<int>(token->type) << ", 字面量: " << token->getLiteralString() << "\n"; } // 若需获取原始类型,用dynamic_cast if (auto* strToken = dynamic_cast<StringToken*>(token.get())) { // 处理字符串字面量 }
优点:兼容旧标准,适合熟悉面向对象多态的开发者;缺点:代码量更大,性能略低于std::variant。
方案3:手动Tagged Union(C++17之前的替代方案)
如果不能用C++17,可手动实现带标签的联合,自行管理类型与存储。
代码示例
#include <string> #include <vector> enum class TokenType { String, Number, Identifier, None }; struct Token { TokenType type; std::string lexeme; int line; // 联合存储不同字面量,需手动处理非POD类型(如string)的构造析构 union { std::string str_val; double num_val; }; // 无字面量Token构造 Token(TokenType t, std::string lex, int ln) : type(t), lexeme(std::move(lex)), line(ln) { if (type == TokenType::String) { new (&str_val) std::string(); // 手动构造string } } // 字符串Token构造 Token(std::string lex, std::string lit, int ln) : Token(TokenType::String, std::move(lex), ln) { str_val = std::move(lit); } // 数字Token构造 Token(std::string lex, double lit, int ln) : type(TokenType::Number), lexeme(std::move(lex)), line(ln) { num_val = lit; } // 手动销毁string ~Token() { if (type == TokenType::String) { str_val.~basic_string(); } } // 禁用拷贝构造/赋值,避免浅拷贝问题 Token(const Token&) = delete; Token& operator=(const Token&) = delete; // 移动构造/赋值 Token(Token&& other) noexcept : type(other.type), lexeme(std::move(other.lexeme)), line(other.line) { if (type == TokenType::String) { new (&str_val) std::string(std::move(other.str_val)); other.str_val.~basic_string(); } else if (type == TokenType::Number) { num_val = other.num_val; } other.type = TokenType::None; } Token& operator=(Token&& other) noexcept { if (this != &other) { // 销毁当前string if (type == TokenType::String) { str_val.~basic_string(); } // 移动数据 type = other.type; lexeme = std::move(other.lexeme); line = other.line; if (type == TokenType::String) { new (&str_val) std::string(std::move(other.str_val)); other.str_val.~basic_string(); } else if (type == TokenType::Number) { num_val = other.num_val; } other.type = TokenType::None; } return *this; } };
优点:兼容老旧C++标准;缺点:代码繁琐,需手动管理构造析构,类型不安全(需严格检查type标签)。
内容的提问来源于stack exchange,提问作者samkimmm012
相关产品推荐
相关产品推荐

