Adapter in C++

Structural Easy

In one sentence: Adapter is a translator object that lets two classes with incompatible interfaces work together, without changing either of them.

Real-world analogy

You land in a country where the wall sockets are a different shape. Your laptop charger works perfectly. The hotel's wiring works perfectly. The two just don't fit — and neither side is yours to change. You can't file down the pins on a moulded plug, and you certainly can't rewire the building.

So you buy a travel plug adapter. One face of it is shaped like the plug the wall expects; the other face has the holes your plug expects. Inside, it does nothing clever: it wires pin to pin. It doesn't charge your laptop faster, doesn't add surge protection, doesn't give you a second USB port. Same electricity, same laptop, same socket — only the shape between them is new.

Fly somewhere else next month and you buy a second adapter, not a second laptop. The thing that gets replaced is always the little brick in the middle.

Mapping the analogy to the pattern:

  • Your laptop's moulded plug → the Adaptee — useful, working, and stuck with the interface it was born with.
  • The socket shape on the wall → the Target interface, the one your surroundings already speak.
  • The adapter brick → the Adapter, which presents the Target's shape outward and the Adaptee's shape inward.
  • You, pushing the plug into the wall → the Client, who does the one thing it always did and never learns a translation happened.
  • Adding no new electrical features → the reason this isn't a Decorator: an adapter changes the shape of the connection, never the behaviour behind it.

Another angle: an interpreter in a meeting. Neither negotiator learns a new language, nobody's argument changes, and the interpreter adds no opinions of their own — they just re-shape each sentence so the other side can receive it.

The problem

Your app collects events as JSON and sends them to an analytics service. Then marketing buys a third-party tracking library — and it only accepts XML.

  1. You can't change the library. It ships as a compiled dependency, and the vendor owns the source.
  2. You don't want to change your app. JSON is used in fifty other places; converting everything to XML to please one library is absurd.
  3. Scattering conversions is worse. Sprinkling jsonToXml() calls at every call site means every future library adds another mess.

This is exactly GoF's motivating story, told with different props: a drawing editor wants every on-screen thing to be a Shape, and a ready-made toolkit already has a perfectly good TextView class — but TextView was never written with Shape in mind, so the two can't be used interchangeably. Editing the toolkit isn't an option, and even if it were, a general-purpose toolkit shouldn't have to adopt one application's domain-specific interface.

The solution

Put a third class in the middle that implements the interface your code already calls, and internally translates each call into whatever the foreign class wants.

  1. Define (or reuse) the interface your app speaks — here, Analytics::send(json).
  2. Write an adapter class that implements that interface and holds a reference to the incompatible object.
  3. Inside each method, convert the arguments and forward the call.

GoF describes two flavours of this, and they are genuinely different designs:

  • Object adapter (composition) — the adapter holds a pointer to the adaptee and forwards to it. This is the version in the example below, and the one you almost always want in modern C++.
  • Class adapter (multiple inheritance) — the adapter inherits from both sides. GoF's C++ convention is to inherit the interface publicly from Target and the implementation privately from Adaptee, so the adapter is a subtype of Target but not of Adaptee.

Note: GoF points out that an adapter isn't limited to renaming methods. It sits on a spectrum: at one end, a trivial name change; at the other, an adapter that implements operations the adaptee simply doesn't have. In GoF's example, TextShape invents CreateManipulator() from scratch because Shape requires drag behaviour that TextView never offered. When you're planning an adapter, first look for the narrow interface — the smallest set of adaptee operations that gets the job done. Narrow interfaces are far easier to adapt than fat ones.

A class that builds adaptation into itself rather than requiring a bespoke wrapper per client is what GoF calls a pluggable adapter — the class takes the narrow interface as an abstract operation, a delegate object, or (in dynamic languages) a pair of blocks, so it can display or operate on hierarchies it was never told about.

Your application code never learns that XML exists. Swap the vendor tomorrow and you rewrite one class.

Structure

Structure of the Adapter pattern Structure of the Adapter pattern
Structure of the Adapter pattern

The diagram shows the object adapter — the composition-based version. (In a class adapter, the dashed adaptee link is replaced by a second inheritance arrow from Adapter to Adaptee.)

  • Target — the domain-specific interface the client already uses. In the example: Analytics.
  • Client — code that only ever talks to Target, and never learns an adaptation happened. In the example: reportDailyEvents.
  • Adaptee — the existing class with a useful implementation but the wrong interface. In the example: LegacyXmlTracker.
  • Adapter — implements Target and translates each call into the adaptee's vocabulary. In the example: XmlTrackerAdapter.

Collaboration is one-directional and dull, which is the point: the client calls an operation on the Adapter, and the Adapter calls whatever Adaptee operations carry out that request.

C++ example

An app that speaks JSON, a legacy tracker that only speaks XML, and the adapter that joins them.

#include <iostream>
#include <memory>
#include <string>
#include <utility>

// The interface our application already speaks.
class Analytics {
public:
    virtual ~Analytics() = default;
    virtual void send(const std::string& jsonEvent) = 0;
};

// A third-party library we cannot modify — it only understands XML.
class LegacyXmlTracker {
public:
    void trackXml(const std::string& xml) const {
        std::cout << "[legacy lib] uploading: " << xml << "\n";
    }
};

// The adapter: Analytics on the outside, XML on the inside.
class XmlTrackerAdapter : public Analytics {
public:
    explicit XmlTrackerAdapter(std::shared_ptr<LegacyXmlTracker> tracker)
        : tracker_(std::move(tracker)) {}

    void send(const std::string& jsonEvent) override {
        tracker_->trackXml(toXml(jsonEvent));
    }

private:
    // Toy conversion: {"name":"signup"} -> <event><name>signup</name></event>
    static std::string toXml(const std::string& json) {
        std::size_t colon = json.find(':');
        std::string value = json.substr(colon + 2, json.size() - colon - 4);
        return "<event><name>" + value + "</name></event>";
    }

    std::shared_ptr<LegacyXmlTracker> tracker_;
};

// Client code — knows nothing about XML.
void reportDailyEvents(Analytics& analytics) {
    analytics.send(R"({"name":"signup"})");
    analytics.send(R"({"name":"purchase"})");
}

int main() {
    auto legacy = std::make_shared<LegacyXmlTracker>();
    auto adapted = std::make_unique<XmlTrackerAdapter>(legacy);
    reportDailyEvents(*adapted);
}
Output
[legacy lib] uploading: <event><name>signup</name></event>
[legacy lib] uploading: <event><name>purchase</name></event>

reportDailyEvents only ever calls send with JSON, yet the legacy library receives valid XML — the translation lives entirely inside the adapter.

When should you use it?

GoF's applicability list has three entries. Use Adapter when:

  • You want to use an existing class whose interface doesn't match the one you need — the everyday case, and the reason most adapters get written.
  • You're writing a reusable class that must cooperate with unrelated or unforeseen classes — classes that have no reason to share your interface, and that may not even exist yet.
  • (object adapter only) You need to work with several existing subclasses, and subclassing every one of them to adapt its interface would be impractical. One object adapter can adapt the parent class and get all the children for free.

Tip: If you own both sides of the mismatch, fix the interface instead of adding an adapter. Adapters earn their keep when one side is genuinely out of your control.

Pros and cons

Pros (GoF's consequences — note that the two flavours differ)

  • Object adapter: one adapter, many adaptees. It works with the Adaptee class and all its subclasses, and can add functionality to all of them at once.
  • Class adapter: no extra indirection. It introduces a single object with no pointer hop to the adaptee, and it can override adaptee behaviour because it is a subclass of the adaptee.
  • Adaptation is contained. Conversion code lives in one class instead of leaking into every call site, so swapping the foreign library touches one file.
  • Pluggable adapters cut assumptions. Building interface adaptation into a class removes the demand that everyone else implement your interface, which makes the class reusable in systems it was never designed for.

Cons

  • Class adapter commits to a concrete adaptee. Since it inherits from a specific class, it won't adapt that class's subclasses — the mirror image of the object adapter's strength.
  • Object adapter makes overriding hard. Changing adaptee behaviour means subclassing the adaptee and pointing the adapter at the subclass instead.
  • Adapters aren't transparent. An adapted object no longer conforms to the adaptee's interface, so it can't be dropped in wherever an adaptee was expected. GoF's fix — a two-way adapter that conforms to both interfaces — costs you multiple inheritance.
  • Adds another class and another indirection to trace through when debugging, and conversions can be lossy or expensive when the two interfaces differ deeply.
  • Too many adapters is a smell — it usually means the codebase never agreed on a common interface.

How to remember it

Memory hook: "The travel plug." Same electricity, different shape — an Adapter changes the interface of something that already works. Contrast the whole wrapper family by what the wrapper does to the interface: Adapter changes it, Decorator keeps it and adds behaviour, Proxy keeps it and controls access, Facade invents a simpler new one over many classes. And against Bridge, the tell is timing: Bridge is designed in up front so two hierarchies can vary independently; Adapter is bolted on afterwards to reconcile code that already clashes.

  • Bridge — GoF notes it has a structure similar to an object adapter but a different intent: Bridge separates an interface from its implementation up front so both can vary; Adapter changes the interface of something that already exists.
  • Decorator — enhances an object without changing its interface, so it's more transparent to the application than an adapter, and unlike pure adapters it supports recursive composition.
  • Proxy — a stand-in for another object that also leaves the interface alone; it controls access rather than translating calls.
  • Facade — wraps a whole subsystem in a new, simpler interface; Adapter matches an existing one.