composite类应由自身独占管理子节点,component接口不定义add/remove等结构操作;leaf仅实现行为方法;遍历须用索引或迭代器避免悬空指针。

Composite 类怎么设计才不崩?
关键不是“怎么写”,而是“谁该负责管理子节点”。Component 接口里不能放 add()、remove() 这类操作——叶子节点根本用不到,强行加进去会逼你写一堆 throw std::runtime_error("not supported"),调用方还得 try-catch,实际运行时才发现错,非常反直觉。
-
Composite类单独继承Component,只在它里面定义add()、remove()、get_child()等树形操作 -
Leaf类也继承Component,但只实现operation()这种行为方法,不碰任何结构相关接口 - 客户端代码拿到一个
Component*指针时,如果真要增删子节点,得先dynamic_cast<composite>(ptr)</composite>判断类型——这不是缺陷,是模式本身的约束:树形操作天然只对容器有意义
递归遍历为什么总漏节点或崩溃?
问题常出在遍历逻辑和生命周期没对齐。比如用 std::vector<:unique_ptr>></:unique_ptr> 存子节点,遍历时用裸指针缓存再递归,但某次 remove() 触发了 vector 重排,缓存指针就悬空了。
- 遍历一律用索引或迭代器(
for (size_t i = 0; i ),别提前提取 <code>children[i].get()存局部变量 - 如果需要多次访问同一子节点,先
auto& child = children[i],用引用保活,而不是Component* p = children[i].get() - 避免在
operation()内部修改自身子节点容器——比如边遍历边remove(),这会破坏迭代器有效性;真要删,先收集待删索引,遍历完再批量 erase
std::shared_ptr 还是 std::unique_ptr?
选 std::unique_ptr 是默认安全选择。复合模式里父子关系明确:父节点拥有子节点,子节点不跨树共享。用 shared_ptr 看似省事,实则埋雷——比如两个 Composite 同时 hold 同一个 Leaf,删除时析构顺序错乱,或者循环引用导致内存泄漏。
- 只有当你明确需要“一个节点被多个父节点引用”(比如 DAG 场景),才考虑
shared_ptr,但这时已超出经典 Composite 范畴 - 若用
unique_ptr,移动语义必须显式支持:Composite的add()接收std::unique_ptr<component>&&</component>,避免拷贝 - 别在接口里暴露裸指针所有权——
get_child(size_t i)返回Component*可以,但release_child(size_t i)这种破坏封装的方法,99% 的场景都不该存在
虚析构函数漏写会怎样?
直接 undefined behavior。哪怕你所有 delete ptr 都写在 Composite 内部,只要存在通过 Component* 删除对象的可能(比如容器统一管理、智能指针类型擦除),没虚析构就是踩内存。
立即学习“C++免费学习笔记(深入)”;
-
Component的析构函数必须声明为virtual ~Component() = default;或至少virtual ~Component() {} - 如果
Component是纯接口(无数据成员、无非虚函数),也得加虚析构——C++ 标准规定:多态基类必须有虚析构,否则 delete 派生类对象时不会调用派生类析构函数 - Clang/GCC 加
-Wdelete-non-virtual-dtor能抓到这类问题,建议编译选项里固定带上
事情说清了就结束。最常被忽略的是:Composite 不是万能树容器,它只解决“统一接口 + 层次结构”的特定组合;一旦出现兄弟节点需通信、路径查找、反向父引用等需求,就得往外抽逻辑,硬塞进 Composite 里只会让 operation() 变成黑洞函数。










