What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Adapter design pattern lets client code use an existing type through the interface it expects. In C++, an adapter may be a class that wraps an object, a template, a lambda, or a standard-library view. Use composition as the usual default for adapting objects; choose templates or views when compile-time checking or lazy range transformation fits better.

What the Adapter pattern does

An adapter sits between a client and an adaptee—the existing type or API—and translates the operations or data between their interfaces. The client calls the interface it already understands; the adapter handles the mismatch, such as different method names, units, argument order, or error representation.

The translation belongs at the boundary. That keeps legacy names and conventions out of the client and lets the adaptee remain unchanged, which is useful when it comes from a third-party library or cannot be edited.

Choose the adapter form that fits the boundary

Form Best fit Main trade-off
Composition-based class A reusable object boundary with explicit forwarding, conversion, or ownership rules. Requires writing the wrapper interface, but exposes only the operations clients need and works with unmodifiable types.
Inheritance-based class A type must satisfy a target interface through inheritance, and the hierarchy and substitutability are appropriate. Couples the adapter to the adaptee’s inheritance structure and can expose more of its interface than intended.
Template or constrained template The adapter can be checked and specialized at compile time, without runtime substitution. Can increase compile-time complexity and produce more involved diagnostics or code-size trade-offs.
Lambda or function object A small, local translation—for example, mapping a name, unit, or call order. Convenient for one-off use, but a named adapter is clearer when ownership, lifetime, or conversion behavior must be documented and reused.
Standard-library view A range needs lazy transformation or composition with other range operations. Views are commonly non-owning, so the source range must outlive their use unless an owning form is used.

Why composition is usually the object-adapter default

A composition-based adapter holds or refers to the adaptee and forwards only the operations the target interface requires. It can translate arguments and results without inheriting the adaptee’s public surface. Make the storage choice deliberate: a reference, smart pointer, value, or owning view implies different ownership and lifetime behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When inheritance makes sense

A class adapter can use inheritance—including multiple inheritance where appropriate—to satisfy a target interface. Use it only when the resulting type relationship is sound and the interface exposure is intended. If the adapter merely needs to call an existing object, composition generally avoids unnecessary coupling.

Use concepts and templates for compile-time adaptation

Templates can express a target interface without virtual dispatch. In C++20, a concept can state the minimum capability required and make an incompatible argument fail at the call site. For example, a range adapter can require an input range:

#include <ranges>
#include <utility>

template<class R>
concept ReadableRange = std::ranges::input_range<R>;

template<ReadableRange R>
auto adapt(R&& r) {
return std::forward<R>(r);
}

This example forwards an input range rather than transforming its elements; the useful point is how the constraint states the accepted interface. Microsoft’s range concepts reference documents concepts declared in std::ranges, including range, borrowed_range, common_range, sized_range, view, and viewable_range.

Use runtime polymorphism when clients genuinely need to substitute implementations after compilation, or when a runtime boundary is part of the design. Prefer constrained templates when the accepted interface is known at compile time and better constraint diagnostics matter. These choices also affect binary boundaries, code size, and dispatch cost, so evaluate them against the actual application rather than assuming one is universally faster or smaller.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use C++20 ranges and views as lazy adapters

Range views adapt a source range into another view and can be piped together. Microsoft describes a view as cheap—O(1)—to copy, assign, and destroy regardless of the number of elements involved. View elements are usually the source range’s actual elements, and a view usually does not own that range; owning_view is an exception. See Microsoft’s range adaptors documentation for the adaptor library and its examples.

auto result = input
| std::views::filter(predicate)
| std::views::transform(project)
| std::views::take(10);

This pipeline filters elements, projects the survivors, and limits the resulting sequence. Because the operations are views, the pipeline describes transformations rather than eagerly building a new container. Keep the source alive for as long as a non-owning view may be used, and consider whether traversing the view more than once is suitable for the source and transformations.

Adapting iterator and sentinel types with common

A range can use different types for its end sentinel and iterator. std::views::common adapts such a range to a view with matching iterator types, which can make it usable with APIs such as legacy std::accumulate that expect a same-type iterator pair. This is a concrete adapter: it changes the interface presented to the consuming algorithm without requiring the original range to change.

Design and review an adapter safely

  1. Define the target interface. Specify only the operations and data the client actually needs.
  2. Keep translation at the boundary. Convert legacy names, units, call sequences, and error types in one place rather than spreading those conventions through client code.
  3. Choose ownership explicitly. Decide whether the adapter stores a value, reference, smart pointer, or owning view; document who keeps the adaptee alive.
  4. Check lifetime assumptions. Non-owning views and span-like adapters do not make their underlying storage live longer.
  5. Choose dispatch deliberately. Use runtime polymorphism only if post-compilation substitution is needed; use concepts and constrained templates when compile-time requirements are the better fit.
  6. Evaluate costs where they matter. Measure conversion and allocation costs for large data or hot paths rather than inferring performance from the pattern name.
  7. Test boundary behavior. Check semantic equivalence, error propagation, cancellation, and exception guarantees—not just whether calls compile.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Further reading

The C++ Core Guidelines describe their aim as helping people use modern C++ effectively and cover interfaces, resource management, memory management, concurrency, architecture, and library design. They define modern C++ as C++11 and newer. For a book-length treatment of patterns, Design Patterns in Modern C++ is a relevant modern C++ design-pattern book.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.