c++适配器模式的关键是明确被适配者、目标接口和桥接者,优先用组合而非继承,避免暴露原始接口,确保生命周期安全。

适配器模式的核心是“接口转换”,不是继承重写
直接说结论:C++ 里实现适配器,关键不是用不用 class 或 template,而是明确谁是被适配者(Adaptee)、谁是目标接口(Target)、谁来桥接(Adapter)。常见错误是把 Adapter 写成对 Adaptee 的简单包装,却忘了暴露一致的 Target 接口——结果调用方仍得知道底层类型,适配就失败了。
典型场景:已有老类 LegacyPrinter 只有 printString(),但新系统只接受 Printable::print();或者第三方库返回 std::vector<int></int>,而你的算法要求输入满足 Range 概念(C++20)。
- 优先用组合(has-a),而非继承(is-a)——除非
Adaptee本就是多态接口且你控制其定义 - 若
Adaptee是具体类、无虚析构,Adapter就不该公开继承它,否则可能误删对象 - 避免在
Adapter里暴露Adaptee的原始接口,否则破坏封装和解耦
对象适配器:用成员变量持有 Adaptee 实例
这是最常用、最安全的方式。适配器内部持有一个 Adaptee 对象(值、引用或指针),通过委托调用其方法,并按 Target 接口重新组织签名和行为。
class Target {
public:
virtual ~Target() = default;
virtual void request() = 0;
};
<p>class Adaptee {
public:
void specificRequest() { /<em> ... </em>/ }
};</p><p>class Adapter : public Target {
private:
Adaptee adaptee<em>; // 值语义,避免生命周期问题
public:
void request() override {
adaptee</em>.specificRequest(); // 转换调用
}
};</p>
注意点:
- 如果
Adaptee构造开销大或不可拷贝,改用std::unique_ptr<adaptee></adaptee>或引用(但需确保引用对象生命周期长于Adapter) - 不要在
Adapter中添加与Target无关的新接口——那不是适配,是混杂设计 - C++17 后可考虑用
[[no_unique_address]]优化空基类(如Adaptee是空类)的内存布局
类适配器:用多重继承(仅限 Adaptee 有虚函数且可控)
仅当 Adaptee 本身是带虚函数的接口类,且你完全掌控其定义时,才考虑用多重继承。现实中极少用,因为 C++ 不支持多继承的构造函数委派,容易引发菱形继承或初始化顺序问题。
class AdapteeInterface {
public:
virtual ~AdapteeInterface() = default;
virtual void doWork() = 0;
};
<p>class Adapter : public Target, private AdapteeInterface {
public:
void request() override { doWork(); } // 必须实现 AdapteeInterface 的纯虚函数
void doWork() override { /<em> ... </em>/ }
};</p>
风险提示:
- 一旦
AdapteeInterface新增虚函数,Adapter编译失败,脆弱性高 - 若
Target和AdapteeInterface都有同名函数(哪怕签名不同),调用会歧义,必须显式限定:Target::request() - 多数现代代码库禁止多重继承,这条路径基本可跳过
函数对象适配器:std::function + lambda 更轻量
当目标只是把一个现有函数/可调用对象“塞进”某个回调签名,别硬套类结构。C++ 标准库的 std::function 本身就是运行时适配器,配合 lambda 可零成本封装。
// 假设旧 API 接受 std::function<void>
// 但你有个成员函数 void Handler::onEvent(const std::string&)
<p>Handler h;
auto adapted = [&h](int x) { h.onEvent(std::to_string(x)); };
legacy_api(adapted);</p></void>
这种写法的优势:
- 无额外类定义,无虚函数开销
- 捕获方式(
[&],[=],[this])决定生命周期依赖,比裸指针更安全 - 若适配逻辑简单(如参数顺序调整、默认值填充),lambda 比写完整
Adapter类清晰十倍
真正容易被忽略的是:适配器的生命周期必须覆盖所有使用它的上下文。无论是 std::unique_ptr<adapter></adapter> 还是 lambda 捕获的引用,一旦被适配的对象提前析构,request() 就变成野指针调用——这比模式本身难调试得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











