如何在不使用指针的情况下实现基于引用的AST构建函数?
我有一个采用访问者模式的抽象语法树(AST)类,所有构造函数与visit函数均接收抽象节点ASTNode或其派生类的引用,相关类定义如下:
class ASTNode { public: virtual void accept(ASTVisitor& visitor) = 0; }; class NameNode : public ASTNode { public: NameNode(std::string name) : name(name) { } void accept(ASTVisitor& visitor) override; std::string name; }; class ListNode : public ASTNode { public: ListNode(ElementSptr list) : list(list) { } void accept(ASTVisitor& visitor) override; ElementSptr list; }; class BinaryOperator : public ASTNode { public: BinaryOperator(std::string kind, ASTNode& left, ASTNode& right) : kind(kind), left(left), right(right) { } void accept(ASTVisitor& visitor) override; std::string kind; ASTNode& left; ASTNode& right; }; class ASTVisitor { public: virtual void visit(ASTNode& node) = 0; virtual void visit(NameNode& name) = 0; virtual void visit(ListNode& list) = 0; virtual void visit(BinaryOperator& bOp) = 0; virtual void visit(UnaryOperator& uOp) = 0; };
我通过独立的TreeBuilder类实现树的构建,其buildTree函数如下:
ASTNode& TreeBuilder::buildTree(std::string expression){ std::vector<std::string> tokens = split(expression); std::deque<ASTNode&> nodeStack;; std::deque<std::string> operatorStack; ASTNode root; ASTNode& left; ASTNode& right; //generates tree from tokens return root; }
由于ASTNode是抽象类,无法直接实例化;定义引用时又提示需初始化。使用指针可解决该问题,但现有树结构依赖引用,无法兼容指针实现。请问是否存在不使用指针即可实现该函数的方法?该函数需返回根节点,供调用类操作其子节点。
不使用裸指针的前提下,可通过以下几种方式解决问题:
用智能指针管理节点生命周期,对外返回引用
在TreeBuilder内部用std::unique_ptr或std::shared_ptr存储AST节点,确保对象生命周期覆盖调用方使用引用的周期。比如将智能指针存入TreeBuilder的成员变量,构建节点时:std::unique_ptr<BinaryOperator> bop = std::make_unique<BinaryOperator>("+", *leftNodeRef, *rightNodeRef); auto& rootRef = *bop; this->nodeStorage.push_back(std::move(bop));最后返回
rootRef即可,只要nodeStorage的生命周期足够长,引用就始终有效。利用
std::variant存储具体节点类型
定义变体类型统一存储所有AST节点的具体实例:using ASTNodeVariant = std::variant<NameNode, ListNode, BinaryOperator>;将
TreeBuilder中的nodeStack替换为std::deque<ASTNodeVariant>,构建节点时直接创建具体类型的实例存入变体,后续通过std::get取出引用传递给构造函数。这种方式能在栈或容器中管理节点,避免直接使用指针,但需注意变体的生命周期要覆盖引用的使用周期。实现AST节点对象池
创建一个对象池类负责在堆上创建并管理AST节点的生命周期,TreeBuilder从池内获取节点引用:class ASTNodePool { public: template<typename T, typename... Args> T& create(Args&&... args) { auto ptr = std::make_unique<T>(std::forward<Args>(args)...); auto& ref = *ptr; nodes.push_back(std::move(ptr)); return ref; } private: std::vector<std::unique_ptr<ASTNode>> nodes; };在
TreeBuilder中持有ASTNodePool实例,构建节点时调用pool.create<BinaryOperator>(...)获取引用,返回的根节点引用也来自池,只要池的生命周期足够长,引用就有效。
核心注意点:引用必须绑定到生命周期足够长的对象上,不能绑定到临时对象或会被提前销毁的栈对象。无论采用哪种方式,都要确保调用方使用根节点及其子节点引用期间,对应的对象不会被销毁。
内容的提问来源于stack exchange,提问作者callum arul

