You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

《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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:24:50