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

C++面向对象:如何在表达式解析中避免类型切换?

解决方案:两种替代动态类型转换的令牌设计方案

针对你在表达式解析器令牌化阶段遇到的类结构设计问题,这里提供两种成熟的替代方案,避免使用dynamic_cast带来的代码异味:

方案一:访问者模式(Visitor Pattern)

访问者模式是处理多态类型的经典方案,它将令牌的操作逻辑与令牌本身分离,既保持了多态性,又避免了显式类型判断。

实现步骤

  1. 定义访问者接口,包含针对每种令牌类型的处理方法
  2. 令牌基类添加纯虚的accept方法,用于接受访问者
  3. 每个令牌子类实现accept方法,调用访问者对应的处理方法
  4. 实现具体的访问者类,封装不同的操作逻辑(比如令牌打印、表达式求值)

代码示例

#include <memory>
#include <vector>
#include <iostream>

// 前置声明访问者基类
class TokenVisitor;

// 令牌基类
class Token {
public:
    virtual ~Token() = default;
    virtual void accept(TokenVisitor& visitor) = 0;
};

// 访问者基类
class TokenVisitor {
public:
    virtual ~TokenVisitor() = default;
    virtual void visit(NumberToken& token) = 0;
    virtual void visit(OperatorToken& token) = 0;
    virtual void visit(BracketToken& token) = 0;
};

// 运算符类型枚举
enum class OperatorType {
    Addition, Subtraction, Multiplication, Division
};

// 括号类型枚举
enum class BracketType {
    Open, Close
};

// 数值令牌
class NumberToken : public Token {
public:
    int value;
    NumberToken(int val) : value(val) {}
    
    void accept(TokenVisitor& visitor) override {
        visitor.visit(*this);
    }
};

// 运算符令牌
class OperatorToken : public Token {
public:
    OperatorType type;
    OperatorToken(OperatorType t) : type(t) {}
    
    void accept(TokenVisitor& visitor) override {
        visitor.visit(*this);
    }
};

// 括号令牌
class BracketToken : public Token {
public:
    BracketType type;
    BracketToken(BracketType t) : type(t) {}
    
    void accept(TokenVisitor& visitor) override {
        visitor.visit(*this);
    }
};

// 示例:打印令牌的访问者
class TokenPrinter : public TokenVisitor {
public:
    void visit(NumberToken& token) override {
        std::cout << "数值令牌:" << token.value << std::endl;
    }
    
    void visit(OperatorToken& token) override {
        const char* opStr = nullptr;
        switch(token.type) {
            case OperatorType::Addition: opStr = "+"; break;
            case OperatorType::Subtraction: opStr = "-"; break;
            case OperatorType::Multiplication: opStr = "*"; break;
            case OperatorType::Division: opStr = "/"; break;
        }
        std::cout << "运算符令牌:" << opStr << std::endl;
    }
    
    void visit(BracketToken& token) override {
        std::cout << "括号令牌:" << (token.type == BracketType::Open ? "(" : ")") << std::endl;
    }
};

// 使用示例
int main() {
    std::vector<std::shared_ptr<Token>> tokens = {
        std::make_shared<NumberToken>(100),
        std::make_shared<OperatorToken>(OperatorType::Addition),
        std::make_shared<BracketToken>(BracketType::Open)
    };
    
    TokenPrinter printer;
    for (auto& token : tokens) {
        token->accept(printer);
    }
    return 0;
}

优点

  • 操作逻辑与令牌类解耦,新增操作只需添加新的访问者类,无需修改令牌代码
  • 完全避免dynamic_cast,代码类型安全且易维护
  • 适合需要多种操作(如打印、求值、语法检查)的复杂场景

方案二:使用std::variant(C++17及以上)

如果你的项目支持C++17或更高版本,std::variant是更轻量的选择。它是类型安全的联合体,无需继承体系,直接存储不同类型的令牌,通过std::visit处理不同类型。

代码示例

#include <variant>
#include <vector>
#include <iostream>

// 运算符类型枚举
enum class OperatorType {
    Addition, Subtraction, Multiplication, Division
};

// 括号类型枚举
enum class BracketType {
    Open, Close
};

// 各个令牌类型(无需继承基类)
struct NumberToken {
    int value;
};

struct OperatorToken {
    OperatorType type;
};

struct BracketToken {
    BracketType type;
};

// 定义令牌变体类型
using Token = std::variant<NumberToken, OperatorToken, BracketToken>;

// 使用示例
int main() {
    std::vector<Token> tokens = {
        NumberToken{100},
        OperatorToken{OperatorType::Addition},
        BracketToken{BracketType::Open}
    };
    
    // 用std::visit处理每个令牌
    for (const auto& token : tokens) {
        std::visit([](const auto& t) {
            using T = std::decay_t<decltype(t)>;
            if constexpr (std::is_same_v<T, NumberToken>) {
                std::cout << "数值令牌:" << t.value << std::endl;
            } else if constexpr (std::is_same_v<T, OperatorToken>) {
                const char* opStr = nullptr;
                switch(t.type) {
                    case OperatorType::Addition: opStr = "+"; break;
                    case OperatorType::Subtraction: opStr = "-"; break;
                    case OperatorType::Multiplication: opStr = "*"; break;
                    case OperatorType::Division: opStr = "/"; break;
                }
                std::cout << "运算符令牌:" << opStr << std::endl;
            } else if constexpr (std::is_same_v<T, BracketToken>) {
                std::cout << "括号令牌:" << (t.type == BracketType::Open ? "(" : ")") << std::endl;
            }
        }, token);
    }
    return 0;
}

优点

  • 无需继承体系,代码更简洁轻便
  • 编译期类型检查,运行时无额外开销
  • 适合操作较少、追求代码简洁的场景

方案选择建议

  • 如果你的解析器需要支持多种独立操作(如求值、格式化、语法验证),优先选择访问者模式,扩展性更好
  • 如果项目使用C++17+且操作逻辑简单,std::variant是更高效简洁的选择

内容的提问来源于stack exchange,提问作者TNTzx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:03:19