桥接模式的核心是将Abstraction与Implementor的绑定从编译期推迟到运行期,通过std::unique_ptr持有接口指针、禁止拷贝、空指针检查及头文件隔离来确保安全与解耦。

桥接模式的核心不是写类,是拆掉 new 的硬编码
桥接模式在 C++ 里真正起作用的地方,从来不是“我写了四个类”,而是你把 Abstraction 和 Implementor 之间的绑定从编译期挪到了运行期。典型错误是:在 Abstraction 构造函数里直接 new ConcreteImplementorA()——这等于没桥接,只是套了层壳。
正确做法是让调用方决定传谁进来:
-
Abstraction持有Implementor*(或std::unique_ptr<implementor></implementor>),不负责创建 - 构造函数接受
Implementor*或移动语义的智能指针 - 避免裸指针生命周期失控:优先用
std::unique_ptr,除非明确需要共享所有权
虚函数表不是免费的:别在 Implementor 接口里塞无意义的纯虚函数
桥接的 Implementor 接口常被设计成“大而全”,比如定义 draw()、resize()、export() 一堆函数,但实际只有两个子类各实现其中 3 个——这会导致所有子类虚函数表膨胀,且违背接口隔离原则。
应该按能力切分:
立即学习“C++免费学习笔记(深入)”;
- 如果
OpenGLRenderer和VulkanRenderer都支持异步提交,就单独抽AsyncCapable接口 -
draw()和clear()属于基础渲染能力,放主Renderer接口 - 子类只继承它真要实现的接口,虚函数表更紧凑,编译器优化也更有效
std::unique_ptr + move 传递比 raw pointer 更安全,但要注意移动后失效
用 std::unique_ptr 实现桥接时,常见崩溃源于二次释放或访问已移走的对象。例如:
Abstraction a(std::make_unique<ConcreteImplementorA>()); Abstraction b = std::move(a); // a.impl_ 现在是 nullptr a.operation(); // 崩溃:解引用 nullptr
所以必须在 Abstraction 的成员函数里加空指针检查,或者干脆禁止拷贝、只允许移动,并文档化行为:
- 在
Abstraction构造函数中用std::move接收std::unique_ptr - 显式删除拷贝构造和拷贝赋值:
Abstraction(const Abstraction&) = delete; - 所有使用
impl_的函数开头加assert(impl_);或抛异常
桥接不是为了解耦而解耦,而是为了应对「实现侧高频变更」
如果你的 Implementor 子类半年都不变一次,或者新增一个平台(比如从 Windows 切到 WebAssembly)要改七八个地方,那大概率桥接没搭对位置。真正的信号是:你经常要给同一个抽象(比如 ImageLoader)临时换底层(从 libpng 切到 stb_image 再切到 WIC),且切换成本低于重编译。
这时候才值得桥接:
- 抽象侧(
ImageLoader)暴露稳定接口:load(const char*)、getPixels() - 实现侧(
PngLoader、StbLoader)只依赖各自 SDK,彼此零耦合 - 上线后热替换 DLL / SO 时,只需更新实现模块,不碰抽象头文件
最易被忽略的一点:桥接之后,Implementor 头文件不该出现在 Abstraction 的头文件里——否则用户包含 Abstraction.h 就被迫依赖所有实现细节。用前向声明 + pImpl 手法隔离。











