委托
在上一篇类的组合之后,我们有一个新的问题需要解决,当两个类需要协作,但生命周期不同步,或者需要动态切换实现时,应该怎么办?
指针连接与 composition by reference
复合(Composition):外部类直接包含内部类的对象,生命周期完全同步。内部对象是外部的一部分,无法动态切换。
委托(Delegation):外部类包含内部类的指针,生命周期不同步。可动态更换内部类对象,关系松散灵活。
复合是“拥有”,委托是“引用”。复合使用实心菱形箭头,委托使用空心菱形箭头。在UML中,空心菱形表示聚合(Aggregation),即通过指针/引用实现的松散拥有关系。
术语辨析:composition by reference
在学术和C++语境中,“by reference”包含指针和引用两种传递方式。因此虽然我们常用指针实现,但更严谨的术语是“composition by reference”,而非“by pointer”。它强调了外部类“持有对内部类的一个引用(指针)”,而不是直接拥有其值。
Pimpl模式(pointer to implementation)

Pimpl 定义:将类的所有私有数据成员和实现细节,全部封装到一个独立的内部类中,外部类只保留一个指向该内部类的指针。全称 Pointer to Implementation。
将类的所有私有数据成员和实现细节,全部封装到一个独立的内部类中,外部类只保留一个指向该内部类的指针。全称 Pointer to Implementation。
经典例子:字符串类
1 | // String.h (外部接口) |
使用者只需包含 String.h,无需知道 StringImpl 的内部细节。修改 StringImpl 的成员时,只重新编译 String.cpp,所有包含 String.h 的文件都不受影响。
这种模式在大型 C++ 项目中(如 Qt、LLVM)被广泛使用,是降低模块耦合度的经典实践。
Handle-Body 模式
Handle(句柄):对外暴露的接口类,包含一个指向 Body 的指针。用户通过 Handle 操作对象,无需了解内部实现。
Body(实现体):实际承担数据和逻辑的类。Handle 将具体操作委托给 Body,Body 可被多个 Handle 共享。
Handle-Body 是 Pimpl 的泛化版本,它不限于隐藏实现,而是更强调“接口与实现分离”的架构思想。在分布式系统、跨平台框架中,Handle-Body 常用于实现多态接口与平台无关的底层实现。
Pimpl 是 Handle-Body 的一种特化形式,专注于“隐藏实现细节”。Handle-Body 更通用,可用于实现多态、延迟加载、共享资源等更广泛的设计场景。
引用计数与 copy-on-write
当多个 Handle 共享同一个 Body 时,如果其中一个 Handle 需要修改数据,不能影响其他 Handle 所看到的同一份数据。如何高效地实现“共享读取、独立写入”?
引用计数:在 Body 中维护一个计数器,记录有多少个 Handle 指向它。当计数为 0 时自动释放 Body 资源。
Copy-on-Write (COW):当 Handle 需要修改 Body 时,先检查引用计数。若计数 > 1,则创建一份新的副本给当前 Handle 修改,原 Body 保留给其他 Handle 共享。
COW 工作流程

COW 是一种惰性复制策略:只在真正需要修改时才复制,从而在共享读取和独立写入之间取得平衡。
C++ 标准库中的 std::string 在早期实现中就曾采用 COW 策略。
引用计数和 COW 虽然高效,但需要注意线程安全问题:在多线程环境下,引用计数的增减和 COW 的复制操作需要加锁保护,否则可能导致数据竞争或内存泄漏。此外,COW 在频繁写入的场景下反而会因多次复制而降低性能





