桥接模式通过组合+接口指针/引用实现抽象与实现的松耦合,支持运行时切换具体实现。abstraction类持有implementor智能指针(推荐unique_ptr),接口聚焦“做什么”,析构需虚析构函数,扩展时须严守维度边界。

桥接模式在 C++ 中不是靠继承绑定抽象和实现,而是用组合 + 接口指针/引用把两者松耦合起来。只要实现类符合接口契约,抽象类就能在运行时自由切换具体实现,无需改代码、不触发重新编译。
为什么不能直接继承实现类
继承会把抽象逻辑和具体实现强耦合,比如 Circle 继承 OpenGLRenderer 后,就再也无法替换成 VulkanRenderer;而桥接模式中 Circle 只依赖 Renderer 接口,Renderer* 指针可随时指向任意具体渲染器对象。
- 继承导致“编译期绑定”,桥接实现“运行时绑定”
- 新增一种渲染器,只需新增
ConcreteRenderer类,不用动任何Shape子类 - 若用多重继承模拟多维度(如
RedCircle、BlueSquare),类数量呈乘积增长,即所谓“类爆炸”
Abstraction 类必须持有 Implementor 的指针或引用
这是桥接成立的物理基础。常见错误是把 Implementor 当作模板参数或静态成员,这会破坏运行时可替换性。
- 推荐用
std::unique_ptr<renderer></renderer>:自动管理生命周期,避免裸指针泄漏 - 慎用
std::shared_ptr<renderer></renderer>:仅当多个Shape实例需共享同一Renderer实例时才考虑 - 绝对避免
Renderer renderer_(值类型成员):会导致对象切片,丢失多态性 - 若
Renderer是轻量无状态接口(如纯虚函数集),引用(Renderer&)也可行,但构造时必须确保被引用对象生命周期长于Shape
接口设计要聚焦“做什么”,而非“怎么做”
Renderer 接口里写 renderCircle(float x, float y, float radius) 是合理的,但写 bindVBO() 或 glDrawArrays() 就越界了——这属于 OpenGL 实现细节,应封装在 OpenGLRenderer 内部。
- 实现接口(
Implementor)只暴露跨平台/跨技术共有的语义操作 - 每个
ConcreteImplementor自行决定底层调用链,比如VulkanRenderer::renderCircle()可能触发 command buffer record + submit,而SoftwareRenderer::renderCircle()只是往像素数组填色 - 如果发现某操作在所有具体实现中逻辑高度一致,说明它本该上移到
Abstraction层,而不是塞进接口
析构顺序和智能指针选择容易出错
常见崩溃场景:Shape 析构时 Renderer 对象已被销毁,但 Shape::~Shape() 仍试图通过悬空指针调用 renderer_->renderCircle()。
- 务必为
Renderer基类声明virtual ~Renderer() = default;,否则delete renderer_行为未定义 - 若用裸指针,
Shape不应负责delete renderer_—— 责任归属必须清晰:谁 new,谁 delete;或者统一交给智能指针 - 最安全做法:在
Shape构造时接收std::unique_ptr<renderer></renderer>,并在成员中保存该智能指针,让 RAII 自动处理释放
真正难的不是写出第一版桥接结构,而是后续扩展时守住接口边界:新增颜色维度时,别把形状逻辑偷偷塞进 Color 类;新增平台维度时,也别让 WindowsAPI 直接调用 Circle::getRadius() —— 桥不断,靠的是双方都守规矩。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











