如何在含异构类型叶子节点的AST中高效获取叶子值?
用
as<T>()替代switch+dynamic_cast,实现更优雅的异构类型转换 你提到的switch+dynamic_cast写法确实维护性和可读性都很差——每次新增一种数据类型,都要去修改switch的分支,代码会越来越臃肿,而且很容易漏写。boost::program_options里的as<T>()方案确实是更优的选择,它把类型转换的逻辑封装起来,调用方的代码会非常简洁。下面我就一步步教你怎么实现这种方式:
核心思路
我们要利用多态+模板方法的组合,把类型转换的逻辑下沉到各个Value子类中,同时给上层提供一个统一的、类型安全的调用接口。最终实现像value->getValue().as<int>()这样的简洁调用。
分步实现
1. 重构基础Value类
首先修改你的基类Value,我们添加一个模板方法as<T>(),同时定义一组受保护的虚函数tryGetAs,用来让子类实现具体的类型转换逻辑:
#include <stdexcept> #include <optional> // 如果想用optional处理失败的话 class Value { public: virtual ~Value() = default; // 方式1:转换失败抛出异常 template<typename T> T as() const { T result; if (!tryGetAs(result)) { throw std::bad_cast{}; // 抛出标准类型转换异常 } return result; } // 方式2:转换失败返回std::optional(更安全,无需处理异常) template<typename T> std::optional<T> as_opt() const { T result; if (tryGetAs(result)) { return result; } return std::nullopt; } // 为了贴合boost的语法,添加getValue方法返回一个包装类 class ValueWrapper { private: const Value* m_inner; public: ValueWrapper(const Value* val) : m_inner(val) {} // 把as方法代理给内部的Value对象 template<typename T> T as() const { return m_inner->as<T>(); } template<typename T> std::optional<T> as_opt() const { return m_inner->as_opt<T>(); } }; ValueWrapper getValue() const { return ValueWrapper(this); } protected: // 为每种支持的类型定义一个tryGetAs重载,子类按需重写 virtual bool tryGetAs(int& out) const { return false; } virtual bool tryGetAs(double& out) const { return false; } // 后续新增类型时,在这里添加对应的重载即可,比如string: // virtual bool tryGetAs(std::string& out) const { return false; } };
2. 实现子类的转换逻辑
接下来让你的IntValue、DoubleValue等子类重写对应的tryGetAs方法:
class IntValue : public Value { private: int m_data; public: IntValue(int val) : m_data(val) {} int getRawValue() const { return m_data; } // 保留原有获取原始值的方法 protected: // 只需要重写int类型的转换逻辑 bool tryGetAs(int& out) const override { out = m_data; return true; } }; class DoubleValue : public Value { private: double m_data; public: DoubleValue(double val) : m_data(val) {} double getRawValue() const { return m_data; } protected: bool tryGetAs(double& out) const override { out = m_data; return true; } };
3. 简化子类实现(可选:用CRTP减少重复代码)
如果要新增很多类型,每个子类都写tryGetAs会有点重复,我们可以用**奇异递归模板模式(CRTP)**来简化:
// 定义CRTP基类,自动实现tryGetAs template<typename Derived, typename T> class ValueCRTP : public Value { protected: bool tryGetAs(T& out) const override { // 转换到子类,调用子类的getRawValue方法 out = static_cast<const Derived*>(this)->getRawValue(); return true; } }; // 现在子类只需要继承ValueCRTP,指定自身类型和对应的数据类型 class IntValue : public ValueCRTP<IntValue, int> { private: int m_data; public: IntValue(int val) : m_data(val) {} int getRawValue() const { return m_data; } }; class DoubleValue : public ValueCRTP<DoubleValue, double> { private: double m_data; public: DoubleValue(double val) : m_data(val) {} double getRawValue() const { return m_data; } };
这样新增类型时,只需要写一个简单的子类,继承ValueCRTP<子类名, 数据类型>,实现getRawValue即可,完全不用管转换逻辑。
调用示例
现在你就可以像boost那样优雅地调用了:
// 假设你从AST中拿到了一个Value指针 Value* astLeaf = new IntValue(42); // 方式1:直接获取,转换失败抛异常 int intVal = astLeaf->getValue().as<int>(); double doubleVal = astLeaf->getValue().as<double>(); // 这里会抛出std::bad_cast // 方式2:用optional安全处理 auto optInt = astLeaf->getValue().as_opt<int>(); if (optInt) { std::cout << "Got int value: " << *optInt << std::endl; } else { std::cout << "Not an int value" << std::endl; }
方案优势对比
| 原switch+dynamic_cast方案 | 新as |
|---|---|
| 新增类型需修改switch分支,维护性差 | 新增类型只需添加子类/重载,无需修改调用代码 |
| 代码冗长,可读性低 | 调用代码简洁直观,一行搞定 |
| 需手动处理dynamic_cast的空指针 | 异常或optional统一处理转换失败,类型更安全 |
内容的提问来源于stack exchange,提问作者Jepessen
相关产品推荐
相关产品推荐

