Design Patterns in C++
Strategy Pattern
Implement the Strategy pattern using polymorphism or functional std::function wrappers.
Interview: Swapping algorithms at runtime, virtual table costs, and comparing static templates vs. dynamic strategies.
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. In modern C++, you can implement this pattern using traditional polymorphism or functional std::function wrappers.
Interchangeable
Encapsulates variations of an algorithm, allowing client code to swap them dynamically at runtime.
std::function
Allows implementing strategies using lambdas or free functions, avoiding virtual table dispatch overhead.
Static strategy
Pass strategies as template arguments to optimize performance by binding them at compile time.
Dynamic vs. Static Strategies
You can implement strategies in two ways:
- Dynamic Strategy: Uses virtual base classes and pointers. This is flexible and allows you to swap strategies at runtime, but introduces virtual table dispatch overhead.
- Static Strategy: Uses templates (e.g.
template <typename Strategy> class Context). This allows the compiler to inline code and optimize performance, but binds the strategy at compile time.
Code Walkthrough
Implementing strategies using polymorphism and functional std::function wrappers.
#include <iostream> #include <vector> #include <functional>// 1. Dynamic Strategy using std::function class TextValidator { private: std::function<bool(const std::string&)> m_strategy;
public: TextValidator(std::function<bool(const std::string&)> strategy) : m_strategy(strategy) {}
void setStrategy(std::function<bool(const std::string&)> strategy) { m_strategy = strategy; }
bool validate(const std::string& text) { return m_strategy ? m_strategy(text) : false; } };
int main() { // Strategies can be passed as simple lambdas auto lowerCaseCheck = [](const std::string& s) { for (char c : s) { if (isupper(c)) return false; } return true; };
TextValidator validator(lowerCaseCheck); std::cout << "Is 'hello' valid? " << std::boolalpha << validator.validate("hello") << "\n";
// Swap strategy at runtime validator.setStrategy([](const std::string& s) { return s.length() > 5; }); std::cout << "Is 'hello' valid? " << validator.validate("hello") << "\n";
return 0; }
Interview-Relevant Information
Q: How does std::function simplify the Strategy pattern?
Answer: std::function allows you to define strategies using any callable object (like lambdas, free functions, or member bind operations). This avoids the need to write complex class hierarchies for simple algorithms.
Q: What is the performance benefit of static strategies using templates?
Answer: Static templates allow the compiler to perform type validation and inline strategy implementations at compile time. This avoids the runtime overhead of virtual table lookups, making static strategies ideal for performance-critical code.
Quick Checklist
Did you evaluate static templates for performance-critical strategies? Do you use std::function for simpler patterns? If yes, your Strategy implementation is optimal.
Use Cases
Defining interchangeable data compression strategies (e.g. ZIP, GZIP, BZIP2) in file systems.
Swapping tax calculation algorithms dynamically based on location data.
Common Mistakes
Creating complex class hierarchies for simple strategies that could be written as basic lambdas.
Using virtual dynamic strategies in performance-critical code where compile-time templates are more efficient.