组合模式的核心是统一接口而非树形结构,Leaf与Composite均实现抽象Component接口,只暴露共同行为;用std::unique_ptr管理所有权,禁止裸指针和dynamic_cast,isComposite()和getChildren()提供安全契约。

组合模式的核心不是“树”,而是统一接口
用 C++ 实现树形结构,最容易掉进的坑是先想着怎么存父子关系、怎么画树、怎么遍历——结果写了一堆 std::vector<node></node> 和 parent 指针,最后发现增删逻辑散落各处,叶子和容器混着处理,add() 对叶子报错、getChildren() 在叶子上返回空 vector 还得判类型。
组合模式的关键在于:让叶子(Leaf)和容器(Composite)都实现同一个接口,比如 Component,且这个接口里只暴露「它们共同能做的事」——通常是 operation()、isComposite() 这类行为抽象,而不是暴露结构细节。
-
Component基类必须是抽象的,至少有一个纯虚函数;否则无法强制子类实现行为 - 不要在基类里塞
addChild()或removeChild()——叶子不该有这些,强行加会导致运行时抛异常或静默失败 - 如果真需要统一调用
add(),就把它做成可选接口(如返回bool),并在叶子中返回false,调用方自行处理失败
如何避免动态_cast 和运行时类型检查
很多人写完发现:要批量执行操作前,得先判断是不是 Composite 才敢 cast 成 Composite* 调 getChildren()。这不是组合模式,这是类型擦除失败。
根本解法是把「是否可组合」变成接口契约,而不是运行时特征:
立即学习“C++免费学习笔记(深入)”;
- 在
Component中定义virtual bool isComposite() const { return false; },Composite重写为true - 提供
virtual std::vector<component> getChildren() const { return {}; }</component>,Composite返回真实子节点,Leaf返回空 vector ——调用方直接遍历,无需 if/cast - 禁止在业务逻辑里出现
dynamic_cast<composite>(ptr)</composite>;一旦出现,说明接口设计没压住类型差异
内存管理:谁 new 谁 delete?RAII 怎么落地
C++ 里树形结构最常崩在析构顺序和悬挂指针上。常见错误是 Composite 持有裸指针,自己不负责 delete,结果叶子对象提前销毁,遍历时访问野指针;或者反过来,Composite 把所有子节点 delete 掉,但外面还有地方拿着同一指针。
推荐做法非常明确:
- 用
std::unique_ptr<component></component>管理子节点所有权 —— Composite 拥有全部子节点,析构时自动释放 - 对外只传
Component&或Component*(非拥有语义),禁止传出裸指针再被外部delete - 如果需要共享访问(比如多个地方要观察同一棵树),改用
std::shared_ptr<component></component>,但注意循环引用:Composite 持有子节点的shared_ptr,子节点绝不能持有父节点的shared_ptr - 别手写
new/delete;哪怕只是 demo,也用std::make_unique初始化
std::vector<:unique_ptr>> 是当前最稳的子节点容器
有人试过 std::list、std::deque,甚至想用 std::array 固定大小——都没必要。组合模式关注的是行为一致性,不是容器性能微差。
std::vector<:unique_ptr>></:unique_ptr> 足够好,原因很实在:
- 插入尾部是 O(1) 均摊,组合树构建以追加为主 遍历效率高,CPU 缓存友好,比链表快得多
- 支持随机索引(虽然组合模式通常不需要),调试时打印
children.size()直观 - 和
unique_ptr配合天然支持移动语义,Composite 可安全被 move 构造/赋值 - 别为了“看起来更像树”而用自定义链式节点结构——那是在重复实现
std::vector的一部分,还容易出错
真正容易被忽略的是:Composite 的拷贝构造函数默认是禁用的(因为 unique_ptr 不可拷贝),如果你需要复制整棵树,得显式写深拷贝逻辑,不能靠编译器生成。


















