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

如何遵循开闭原则实现可扩展的表达式树节点?

表达式树的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;
}

关键优化点说明

  1. 抽象基类Op:将所有核心方法设为纯虚函数,确保Op无法被直接实例化,强制所有派生类必须实现这些方法,符合抽象接口的设计意图。
  2. 智能指针持有Op:TreeNode使用std::unique_ptr<Op>管理Op实例,既避免了手动内存管理的风险,又完美支持多态,不会出现对象切片问题。
  3. 封装TreeNode成员:将m_left、m_right设为私有,通过公共接口访问,符合面向对象的封装原则。
  4. 递归析构: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 11:05:29