访问者模式核心结构由Element基类(含虚accept)、ConcreteElement实现(调用v.visit(*this))和Visitor纯虚基类(声明各visit重载)构成,依赖双重分派与前向声明解循环依赖。

访问者模式的核心结构怎么写
访问者模式的关键在于分离「数据结构」和「操作逻辑」,C++里靠双重分发实现:先由元素对象调用 accept,再由访问者调用对应类型的 visit。必须用虚函数 + 重载配合,不能只靠模板或函数重载单层解决。
典型结构包括三类角色:Element(基类带 accept(Visitor&))、ConcreteElementA/B(各自实现 accept 调用 v.visit(*this))、Visitor(纯虚基类,声明多个 visit 重载)。
常见错误是把 visit 声明成模板函数——这会破坏运行时多态,导致编译器无法在访问者侧正确绑定到具体元素类型。
为什么 visit 函数参数必须是具体类型而非基类引用
因为访问者需要针对不同元素执行差异化逻辑,比如 visit(const Shape&) 和 visit(const Text&) 行为完全不同。如果全写成 visit(const Element&),就退化成普通虚函数调用,失去“访问者”意义。
实操要点:
-
Visitor接口中每个visit函数参数类型必须与具体元素类一一对应 - 所有
ConcreteElement::accept必须显式传入*this(不是static_cast<const element>(*this)</const>),否则重载解析失败 - 若元素类有继承体系(如
Circle继承Shape),visit参数应声明为const Circle&,而不是基类,否则丢失子类特有成员访问权
如何避免 double dispatch 的循环头文件依赖
元素头文件要包含访问者头文件(因 accept 需调用 v.visit(*this)),而访问者头文件又要包含所有元素头文件(因需声明各 visit 函数)。直接包含会报错。
解决方案是前向声明 + 分离定义:
- 在
visitor.h中前向声明所有ConcreteElementX类,并只声明visit函数签名 - 在
visitor.cpp中才#include各元素头文件,定义全部visit函数体 - 元素头文件中只包含
visitor.h,并在accept实现里用指针或引用调用(不涉及具体布局)
否则会出现 “‘ConcreteElementA’ is not defined” 或 “invalid use of incomplete type” 错误。
std::variant + std::visit 能替代传统访问者吗
可以简化简单场景,但不是等价替代。比如用 std::variant<circle rect text></circle> 配合 std::visit,能免去手写 accept 和虚函数表开销。
但要注意:
- 所有类型必须在编译期确定,无法动态扩展新元素类型(不像传统访问者可新增
ConcreteElementZ并只改访问者实现) - 不能共享访问状态(如遍历时累计面积),除非用 lambda 捕获外部变量,但易引发生命周期问题
- 无法对同一元素集合做多种不同遍历逻辑(如渲染 vs 导出 JSON),因为
std::visit是一次性函数对象调用
真正复杂的领域模型(如 AST、GUI 控件树)还是得回归经典访问者;临时聚合数据用 std::variant 更轻量。
最易被忽略的是:当元素本身带有虚析构函数或需要多态销毁时,std::variant 存储值语义会切片,必须转用 std::unique_ptr<element></element> + 传统方式。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











