如何遵循开闭原则实现可扩展的表达式树节点?
表达式树的Op抽象类与TreeNode优化实现
问题背景
我正在为表达式树定义TreeNode类,希望将操作符和操作数统一抽象为Op类。理想情况下Op应该是抽象接口,但直接在TreeNode中用Op作为成员变量会因为抽象类无法实例化而报错。目前用了伪抽象基类(给虚函数默认实现),但肯定有更优方案。目标是遵循开闭原则:新增一元/二元/三元操作符、加减除或自定义操作符时,只需新增Op的派生类,无需修改原有代码。
当前实现代码存在对象切片问题:TreeNode中的Op m_op是基类对象,赋值派生类实例时会丢失派生类特有的数据和行为,导致多态失效。
优化方案
核心思路是让TreeNode持有Op的指针或智能指针,而非直接持有基类对象,这样既能利用抽象类的多态特性,又避免切片问题。同时将Op改为真正的抽象基类(包含纯虚函数),确保它不能被直接实例化。
优化后的完整代码
#include <memory> #include <string> #include <iostream> // 真正的抽象基类Op class Op { public: virtual bool is_operand() const = 0; virtual bool is_operator() const = 0; virtual std::string get_name() const = 0; virtual ~Op() = default; // 默认虚析构,确保派生类析构被正确调用 }; // 加法操作符派生类 class Add : public Op { public: bool is_operand() const override { return false; } bool is_operator() const override { return true; } std::string get_name() const override { return "add"; } }; // 整数操作数派生类 class IntOperand : public Op { private: int m_value; public: IntOperand(int val) : m_value(val) {} bool is_operand() const override { return true; } bool is_operator() const override { return false; } std::string get_name() const override { return std::to_string(m_value); } }; // 变量操作数派生类 class VarOperand : public Op { private: std::string m_value; public: VarOperand(const std::string& str) : m_value(str) {} bool is_operand() const override { return true; } bool is_operator() const override { return false; } std::string get_name() const override { return m_value; } }; // 表达式树节点类 class TreeNode { private: std::unique_ptr<Op> m_op; // 用智能指针管理Op实例,自动释放内存 TreeNode* m_left = nullptr; TreeNode* m_right = nullptr; public: // 构造函数接收Op的智能指针 TreeNode(std::unique_ptr<Op> op) : m_op(std::move(op)) {} // 提供访问成员的接口(替代直接暴露public成员) Op& get_op() const { return *m_op; } TreeNode*& get_left() { return m_left; } TreeNode*& get_right() { return m_right; } // 析构函数,递归释放子节点 ~TreeNode() { delete m_left; delete m_right; } }; int main() { // 创建加法节点作为根 auto root = std::make_unique<TreeNode>(std::make_unique<Add>()); // 创建变量节点d auto d_node = std::make_unique<TreeNode>(std::make_unique<VarOperand>("d")); root->get_left() = d_node.release(); // 释放智能指针所有权给TreeNode // 创建另一个加法节点 auto add_node = std::make_unique<TreeNode>(std::make_unique<Add>()); root->get_right() = add_node.release(); // 测试多态调用 std::cout << "Root node type: " << (root->get_op().is_operator() ? "Operator" : "Operand") << ", name: " << root->get_op().get_name() << std::endl; std::cout << "Left child type: " << (root->get_left()->get_op().is_operand() ? "Operand" : "Operator") << ", name: " << root->get_left()->get_op().get_name() << std::endl; }
关键优化点说明
- 抽象基类Op:将所有核心方法设为纯虚函数,确保Op无法被直接实例化,强制所有派生类必须实现这些方法,符合抽象接口的设计意图。
- 智能指针持有Op:TreeNode使用
std::unique_ptr<Op>管理Op实例,既避免了手动内存管理的风险,又完美支持多态,不会出现对象切片问题。 - 封装TreeNode成员:将
m_left、m_right设为私有,通过公共接口访问,符合面向对象的封装原则。 - 递归析构:TreeNode的析构函数自动递归释放子节点,避免内存泄漏。
开闭原则的体现
新增操作符(比如乘法Mul)或操作数(比如FloatOperand)时,只需新增对应的Op派生类,无需修改TreeNode或原有Op类的代码:
class Mul : public Op { public: bool is_operand() const override { return false; } bool is_operator() const override { return true; } std::string get_name() const override { return "mul"; } };
直接在main中使用即可:
auto mul_node = std::make_unique<TreeNode>(std::make_unique<Mul>());
内容的提问来源于stack exchange,提问作者Jeremy Roberson
相关产品推荐
相关产品推荐

