Composite模式适用场景是需统一处理单个与多个对象且接口一致时;否则用vector+虚函数即可。须声明虚析构函数,add/remove仅在Composite实现,children容器统一用unique_ptr,vector为默认容器选择。

组合模式的核心判断:什么时候该用 Composite 而不是裸指针树?
当你需要统一处理单个对象和一组对象(比如遍历、计算、序列化),且它们对外暴露相同接口时,Composite 才真正必要。如果只是存孩子、递归打印、简单增删,用 std::vector<:unique_ptr>></:unique_ptr> 配合虚函数就够了——别过早套模式。
常见错误现象:std::bad_cast 或运行时崩溃,往往是因为父类没声明虚析构,或子类忘了 override 接口函数。
- 必须给基类加
virtual ~Component() = default;,否则delete派生对象会漏掉子类析构逻辑 -
add()/remove()只应在Composite中实现;叶子类(Leaf)里直接抛std::runtime_error("not supported"),比返回错误码更明确 - 避免在
Composite的children容器里混存裸指针和智能指针——统一用std::unique_ptr<component></component>,否则所有权混乱
std::vector 存孩子 vs std::list vs std::forward_list 怎么选?
绝大多数场景下,std::vector<:unique_ptr>></:unique_ptr> 是默认起点。它局部性好、遍历快、内存紧凑,而且 push_back() 均摊 O(1)。
只有当频繁在中间插入/删除(比如按优先级重排子节点),才考虑 std::list;std::forward_list 除非你确定只从前向后遍历且极度抠内存,否则没必要。
立即学习“C++免费学习笔记(深入)”;
-
vector在clear()后不释放内存,但shrink_to_fit()可显式回收(注意:非强制) - 如果节点数极少(std::array<:unique_ptr>, 4> + 计数器,避免堆分配开销
- 别用
std::shared_ptr管理 children——父子引用容易导致循环持有,析构卡死
遍历子树时,递归爆栈 or 迭代太绕?
深度超过几百层就可能触发栈溢出,尤其 Debug 模式下栈帧更大。这时候必须换迭代写法,但不用手写栈模拟——用 std::stack 或 std::queue 更安全。
示例:深度优先遍历(DFS)
void Composite::dfs_traverse() const {
std::stack<const Component*> stk;
stk.push(this);
while (!stk.empty()) {
const auto* node = stk.top(); stk.pop();
node->operation(); // 统一接口
if (const auto* comp = dynamic_cast<const Composite*>(node)) {
for (auto it = comp->children.rbegin(); it != comp->children.rend(); ++it) {
stk.push(it->get());
}
}
}
}
-
dynamic_cast失败返回nullptr,所以要判空;启用 RTTI 是前提(MSVC 默认开,GCC/Clang 加-frtti) - 用
rbegin()/rend()是为了保持和递归版一样的访问顺序(从左到右) - 如果只需要广度优先(BFS),把
std::stack换成std::queue即可
序列化到 JSON 时,Composite 和 Leaf 怎么共用一套逻辑?
关键不是“怎么序列化”,而是“怎么避免重复写 if (is_composite)”。推荐用虚函数 + nlohmann::json 的隐式转换支持:
在基类声明:virtual nlohmann::json to_json() const = 0;,然后让 Composite::to_json() 返回含 "type": "composite" 和 "children" 数组的对象,Leaf::to_json() 返回带 "type": "leaf" 和自身字段的 object。
- 别在
to_json()里直接调json["children"] = children——children是vector<unique_ptr></unique_ptr>,需手动转成vector<json></json> - 如果字段名要统一小写,或忽略某些字段,在每个子类的
to_json()里控制,不要试图在基类加通用过滤器 - 反序列化时无法靠虚函数自动分发,得先读
"type"字段,再 new 对应类型——这是组合模式天然的不对称点,接受它
最易被忽略的是深拷贝:如果节点里存了原始指针、文件句柄或 GPU 资源,clone() 函数不能只 shallow copy —— 但多数业务模型根本不需要 clone,别提前实现。











