The Adapter pattern converts the interface of one class into another interface clients expect. It lets two pieces of code work together when their interfaces were designed for different assumptions. The adapter sits between them, translates calls, and leaves both sides unchanged. C++ programmers meet this pattern constantly in the standard library, usually under the name "adaptor".
The problem
You have a useful class or C API with the right behavior but the wrong shape. Your code expects read_celsius(), and the library offers legacy_read_fahrenheit(handle, &value). Changing either side is expensive or impossible. Copying the behavior into a new class duplicates logic and creates two versions to maintain.
Adapter wraps the incompatible object and exposes the interface your code already uses. The client keeps calling the same method. The adaptee keeps its own interface. Only the wrapper knows about both.
Typical cases:
- a third-party library you cannot edit
- a legacy C API with handles and error codes
- standard library components whose interface does not fit your loop, such as
std::reverse_iteratororstd::back_inserter - two subsystems that should not include each other's headers
Structure
| Role | Responsibility |
|---|---|
| Target | The interface the client already uses |
| Client | Code that calls the target interface |
| Adaptee | The existing class or API with the incompatible interface |
| Adapter | Translates target calls into adaptee calls |
Object adapter and class adapter
An object adapter holds a reference or pointer to the adaptee and forwards calls. This is the usual choice in C++. It works with classes marked final, it does not expose the adaptee's implementation, and the adaptee can be replaced at runtime.
class Target {
public:
virtual ~Target() = default;
virtual void request() = 0;
};
class Adaptee {
public:
void specific_request() { /* ... */ }
};
class ObjectAdapter : public Target {
public:
explicit ObjectAdapter(Adaptee& adaptee) : adaptee_(adaptee) {}
void request() override {
adaptee_.specific_request();
}
private:
Adaptee& adaptee_;
};
A class adapter inherits from both the target and the adaptee. It saves one indirection, but it requires multiple inheritance, cannot adapt a final class, and ties the adapter to the adaptee's protected members. Use it only when the extra inheritance buys something concrete.
class ClassAdapter : public Target, private Adaptee {
public:
void request() override {
specific_request();
}
};
A legacy C API
Suppose an application works with this interface:
#include <iostream>
#include <memory>
#include <stdexcept>
class Sensor {
public:
virtual ~Sensor() = default;
virtual double read_celsius() const = 0;
};
The sensor vendor ships a C API that returns Fahrenheit through an opaque handle:
extern "C" {
struct legacy_sensor;
legacy_sensor* legacy_open(const char* device);
void legacy_close(legacy_sensor* sensor);
int legacy_read_fahrenheit(legacy_sensor* sensor, double* out);
}
The adapter owns the handle, checks the error code, and converts the unit:
class LegacySensorAdapter : public Sensor {
public:
explicit LegacySensorAdapter(const char* device)
: handle_(legacy_open(device), &legacy_close) {
if (!handle_) {
throw std::runtime_error("cannot open sensor");
}
}
double read_celsius() const override {
double fahrenheit = 0.0;
if (legacy_read_fahrenheit(handle_.get(), &fahrenheit) != 0) {
throw std::runtime_error("sensor read failed");
}
return (fahrenheit - 32.0) * 5.0 / 9.0;
}
private:
std::unique_ptr<legacy_sensor, void (*)(legacy_sensor*)> handle_;
};
Client code does not need the C header, the handle, or the conversion:
void log_temperature(const Sensor& sensor) {
std::cout << sensor.read_celsius() << " C\n";
}
int main() {
LegacySensorAdapter sensor("/dev/sensor0");
log_temperature(sensor);
}
This is the pattern at its most useful: a small class that keeps a vendor detail out of the rest of the program.
Adapter in the C++ standard library
The standard library uses adapters in three places.
Container adaptors give an existing sequence container a narrower interface. std::stack<T> uses std::deque<T> by default and exposes only push, pop, top, empty, and size. std::queue and std::priority_queue work the same way. You can choose a different underlying container as a template argument, but the adaptor still controls the interface. See Container adapters.
Iterator adapters wrap an iterator and change what it does. std::reverse_iterator flips the direction of traversal. std::back_insert_iterator turns assignment into push_back. std::move_iterator makes dereference produce an rvalue reference. Stream iterators present a stream as an iterator. See Iterator adapters.
Callable adapters change the shape of a function object. std::bind fills in or reorders arguments. std::mem_fn turns a pointer to member into a callable. std::not_fn negates a predicate. std::function erases a callable's type behind a fixed signature. See Callable adapters.
When to use Adapter
Use Adapter when you cannot change either side and the seam between them needs a name. It also helps when several incompatible APIs should look the same to the rest of the code, or when vendor headers should stay out of your domain types.
Avoid it when you control both interfaces. Change the interface instead. A one-line forwarding adapter that adds no translation is usually noise. If the wrapper adds behavior rather than converting an interface, you want Decorator. If it simplifies a whole subsystem, you want Facade.
Trade-offs
| Aspect | Benefit | Cost |
|---|---|---|
| Interface fit | Existing code and libraries work together unchanged | One more type and one more indirection |
| Isolation | Vendor headers and conventions stay in one place | The adapter's API can hide useful features |
| Testing | A fake adaptee is easy to substitute | The adapter itself needs tests for conversion and errors |
| Ownership | The adapter can own, borrow, or reference the adaptee | Lifetime rules must be explicit |
| Composition | Several adapters can normalize several APIs | Too many thin wrappers make the call graph harder to follow |
Related patterns
- Decorator has the same wrapper shape but adds behavior instead of converting an interface.
- Facade simplifies a set of interfaces; Adapter matches one specific target interface.
- Proxy exposes the same interface as the wrapped object and controls access.
- Bridge separates abstraction from implementation up front; Adapter fixes a mismatch after the fact.
- Strategy swaps algorithms; Adapter swaps interfaces.
- Iterator is often the client of an iterator adapter.
Summary
Adapter is small and unglamorous, which is why it works. It earns its place when you cannot change either side and the translation has to live somewhere. If you can change the interface, do that instead. The standard library's container, iterator, and callable adaptors show the pattern at its best: thin wrappers that make existing components fit a protocol without copying their logic.