Prefer callables over inheritance for simple behavioral patterns
Use free functions, lambdas, or other callable objects instead of inheritance hierarchies for simple behavioral customization.
Callable objects offer:
- Value Semantics - avoids complexities and risks from Reference Semantics
- Zero-overhead abstractions - lambdas can be inlined completely
- Composability - can be stored, passed, and combined easily
- Leaner - less boilerplate (no base class, virtual destructor)
- Loose coupling - no dependency on a specific base class; easy to extend or substitute behavior
Inheritance-based approach
Section titled “Inheritance-based approach”#include <algorithm>class TextFormatter {public: virtual std::string format(const std::string& text) const = 0; virtual ~TextFormatter() = default;};
std::string format_text(const std::string& text, const TextFormatter& formatter){ return formatter.format(text);}
class UpperCaseFormatter : public TextFormatter {public: std::string format(const std::string& text) const override { std::string result = text; std::transform(result.begin(), result.end(), result.begin(), ::toupper); return result; }};
// Usageauto formatted = format_text("Hello world", UpperCaseFormatter{});Callable approach
Section titled “Callable approach”#include <algorithm>using TextFormatter = std::function<std::string(const std::string&)>;
std::string format_text(const std::string& text, const TextFormatter& formatter){ return formatter(text);}
TextFormatter toUppercase = [](const std::string& text) { std::string result = text; std::transform(text.begin(), text.end(), result.begin(), ::toupper); return result;};
// Usageauto formatted = format_text("Hello world", toUppercase);#include <algorithm>template<typename Formatter>auto format_text(const std::string& text, Formatter formatter){ return formatter(text);}
auto toUppercase = [](const std::string& text) { std::string result = text; std::transform(text.begin(), text.end(), result.begin(), ::toupper); return result;};
// Usageauto formatted = format_text("Hello world", toUppercase);#include <algorithm>#include <concepts>#include <ranges>
auto format_text(const std::string& text, std::invocable<const std::string&> auto formatter) { return formatter(text);}
auto toUppercase = [](const std::string& text) { std::string result = text; std::ranges::transform(text, result.begin(), ::toupper); return result;};
// Usageauto formatted = format_text("Hello world", toUppercase);When this applies
Section titled “When this applies”This guideline works best for:
- Simple behavioral variations
- Stateless or minimal state operations
- Cases where the “strategy” is just an algorithm
For complex strategies with significant state or multiple methods, inheritance may still be appropriate.
Related
Section titled “Related”- Prefer value semantics over reference semantics - The fundamental principle
- Strategy Pattern - Where this guideline often applies
- Command Pattern - Another pattern that benefits from this approach