C++ Strategy Pattern: Complete Guide | Algorithms, Lambdas & std::function
이 글의 핵심
Strategy pattern in C++: swap algorithms at runtime—context, concrete strategies, factories, and comparison with State pattern.
Behavioral patterns are covered in C++ behavioral patterns #20-1 and the overview #20-2.
What is Strategy Pattern? why you need it
Problem Scenario: Hardcoding the Algorithm
Problem: If the logic for selecting a sorting algorithm is hardcoded in the Context, Context must be modified when adding a new algorithm.
// Bad example: hardcoding the algorithm
class Sorter {
public:
void sort(std::vector<int>& data, const std::string& algorithm) {
if (algorithm == "bubble") {
// bubble sort
} else if (algorithm == "quick") {
// quick sort
} else if (algorithm == "merge") {
// merge sort
}
// Edit here when adding a new algorithm
}
};
Solution: Strategy Pattern encapsulates the algorithm** so that it can be replaced at runtime.
// Good example: Strategy Pattern
class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual ~SortStrategy() = default;
};
class BubbleSort : public SortStrategy {
void sort(std::vector<int>& data) override { /* ... */ }
};
class Sorter {
public:
void setStrategy(std::unique_ptr<SortStrategy> s) {
strategy = std::move(s);
}
void sort(std::vector<int>& data) {
strategy->sort(data);
}
private:
std::unique_ptr<SortStrategy> strategy;
};
flowchart TD
context["Context (Sorter)"]
strategy["Strategy (SortStrategy)"]
bubble["BubbleSort"]
quick["QuickSort"]
merge["MergeSort"]
context --> strategy
strategy <|-- bubble
strategy <|-- quick
strategy <|-- merge
index
- Basic structure (polymorphic)
- Function pointer method
- Lambda method
- std::function method
- Frequently occurring problems and solutions
- Production Patterns
- Complete example: Compression algorithm
- Performance comparison
1. Basic structure (polymorphism)
Minimum Strategy
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual std::string name() const = 0;
virtual ~SortStrategy() = default;
};
class BubbleSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
std::string name() const override { return "BubbleSort"; }
};
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::sort(data.begin(), data.end());
}
std::string name() const override { return "QuickSort"; }
};
class Sorter {
public:
void setStrategy(std::unique_ptr<SortStrategy> s) {
strategy = std::move(s);
}
void sort(std::vector<int>& data) {
if (strategy) {
std::cout << "Using " << strategy->name() << '\n';
strategy->sort(data);
}
}
private:
std::unique_ptr<SortStrategy> strategy;
};
int main() {
Sorter sorter;
std::vector<int> data = {5, 2, 8, 1, 9};
sorter.setStrategy(std::make_unique<BubbleSort>());
sorter.sort(data); // Using BubbleSort
sorter.setStrategy(std::make_unique<QuickSort>());
sorter.sort(data); // Using QuickSort
}
2. function pointer method
Simple algorithm
#include <iostream>
#include <vector>
#include <algorithm>
using SortFunc = void(*)(std::vector<int>&);
void bubbleSort(std::vector<int>& data) {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
void quickSort(std::vector<int>& data) {
std::sort(data.begin(), data.end());
}
class Sorter {
public:
void setStrategy(SortFunc func) {
strategy = func;
}
void sort(std::vector<int>& data) {
if (strategy) {
strategy(data);
}
}
private:
SortFunc strategy = nullptr;
};
int main() {
Sorter sorter;
std::vector<int> data = {5, 2, 8, 1, 9};
sorter.setStrategy(bubbleSort);
sorter.sort(data);
sorter.setStrategy(quickSort);
sorter.sort(data);
}
3. lambda method
Inline Algorithm
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
class Sorter {
public:
using Strategy = std::function<void(std::vector<int>&)>;
void setStrategy(Strategy s) {
strategy = s;
}
void sort(std::vector<int>& data) {
if (strategy) {
strategy(data);
}
}
private:
Strategy strategy;
};
int main() {
Sorter sorter;
std::vector<int> data = {5, 2, 8, 1, 9};
// Define Strategy with lambda
sorter.setStrategy( {
std::sort(data.begin(), data.end());
});
sorter.sort(data);
// Sort in reverse order
sorter.setStrategy( {
std::sort(data.begin(), data.end(), std::greater<>());
});
sorter.sort(data);
}
4. std::function method
Flexible Strategy
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
class PaymentStrategy {
public:
using Strategy = std::function<bool(double)>;
void setStrategy(Strategy s) {
strategy = s;
}
bool pay(double amount) {
if (strategy) {
return strategy(amount);
}
return false;
}
private:
Strategy strategy;
};
int main() {
PaymentStrategy payment;
// credit card
payment.setStrategy( {
std::cout << "Paying $" << amount << " with Credit Card\n";
return true;
});
payment.pay(100.0);
// PayPal
payment.setStrategy( {
std::cout << "Paying $" << amount << " with PayPal\n";
return true;
});
payment.pay(50.0);
}
5. Frequently occurring problems and solutions
Problem 1: Strategy nullptr
Symptom: Crash.
Cause: Strategy is not set.
// ❌ Misuse: No nullptr check
void sort(std::vector<int>& data) {
strategy->sort(data); // Crash: nullptr
}
// ✅ Correct usage: nullptr check
void sort(std::vector<int>& data) {
if (strategy) {
strategy->sort(data);
} else {
throw std::runtime_error("Strategy not set");
}
}
Problem 2: State sharing
Symptom: Behavior different from expected.
Cause: If Strategy has state, it becomes problematic when reused.
// ❌ Misuse: state sharing
class CountingSort : public SortStrategy {
int count = 0; // situation
public:
void sort(std::vector<int>& data) override {
++count; // Accumulation when reused
}
};
// ✅ Correct use: Stateless Strategy
class CountingSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
// no state, pure algorithm
}
};
6. production pattern
Pattern 1: Basic Strategy
class Sorter {
public:
Sorter() : strategy(std::make_unique<QuickSort>()) {} // default value
void setStrategy(std::unique_ptr<SortStrategy> s) {
if (s) {
strategy = std::move(s);
}
}
void sort(std::vector<int>& data) {
strategy->sort(data); // always valid
}
private:
std::unique_ptr<SortStrategy> strategy;
};
Pattern 2: Strategy Factory
class StrategyFactory {
public:
static std::unique_ptr<SortStrategy> create(const std::string& type) {
if (type == "bubble") return std::make_unique<BubbleSort>();
if (type == "quick") return std::make_unique<QuickSort>();
return nullptr;
}
};
int main() {
Sorter sorter;
sorter.setStrategy(StrategyFactory::create("quick"));
}
7. Complete Example: Compression Algorithm
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class CompressionStrategy {
public:
virtual std::vector<uint8_t> compress(const std::string& data) = 0;
virtual std::string decompress(const std::vector<uint8_t>& data) = 0;
virtual std::string name() const = 0;
virtual ~CompressionStrategy() = default;
};
class ZipCompression : public CompressionStrategy {
public:
std::vector<uint8_t> compress(const std::string& data) override {
std::cout << "[ZIP] Compressing " << data.size() << " bytes\n";
std::vector<uint8_t> result(data.begin(), data.end());
return result;
}
std::string decompress(const std::vector<uint8_t>& data) override {
std::cout << "[ZIP] Decompressing " << data.size() << " bytes\n";
return std::string(data.begin(), data.end());
}
std::string name() const override { return "ZIP"; }
};
class GzipCompression : public CompressionStrategy {
public:
std::vector<uint8_t> compress(const std::string& data) override {
std::cout << "[GZIP] Compressing " << data.size() << " bytes\n";
std::vector<uint8_t> result(data.begin(), data.end());
return result;
}
std::string decompress(const std::vector<uint8_t>& data) override {
std::cout << "[GZIP] Decompressing " << data.size() << " bytes\n";
return std::string(data.begin(), data.end());
}
std::string name() const override { return "GZIP"; }
};
class Compressor {
public:
void setStrategy(std::unique_ptr<CompressionStrategy> s) {
strategy = std::move(s);
}
std::vector<uint8_t> compress(const std::string& data) {
if (!strategy) {
throw std::runtime_error("Compression strategy not set");
}
std::cout << "Using " << strategy->name() << " compression\n";
return strategy->compress(data);
}
std::string decompress(const std::vector<uint8_t>& data) {
if (!strategy) {
throw std::runtime_error("Compression strategy not set");
}
return strategy->decompress(data);
}
private:
std::unique_ptr<CompressionStrategy> strategy;
};
int main() {
Compressor compressor;
std::string data = "Hello, World! This is a test.";
compressor.setStrategy(std::make_unique<ZipCompression>());
auto compressed = compressor.compress(data);
auto decompressed = compressor.decompress(compressed);
std::cout << "Result: " << decompressed << "\n\n";
compressor.setStrategy(std::make_unique<GzipCompression>());
compressed = compressor.compress(data);
decompressed = compressor.decompress(compressed);
std::cout << "Result: " << decompressed << '\n';
}
8. Performance comparison
| method | Advantages | Disadvantages |
|---|---|---|
| polymorphism | Type safe, extensible | vtable overhead, heap allocation |
| Function pointer | Fast, simple | Lack of type safety, no condition |
| Lambda | Inline, Capture Capable | Complex type, difficult to debug |
| std::function | Flexible, all callable | Large overhead, heap allocation |
organize
| concept | Description |
|---|---|
| Strategy Pattern | Runtime replacement by encapsulating algorithms |
| Purpose | Algorithm independence, scalability |
| Structure | Context, Strategy, ConcreteStrategy |
| Advantages | OCP compliance, removal of conditional statements, easy testing |
| Disadvantages | class increment, indirect reference |
| Use Case | Sorting, Compression, Payment, Routing |
The Strategy Pattern is a powerful design pattern for situations where algorithms need to be replaced dynamically.
FAQ
Q1: When do I use Strategy Pattern?
A: Used when you need to choose between multiple algorithms and replace them at runtime.
Q2: Polymorphism vs Lambda?
A: If scalability is important, use polymorphism, and if simple algorithm, use lambda.
Q3: What is the difference from State Pattern?
A: Strategy focuses on algorithm replacement, State focuses on state transition.
Q4: What is the performance overhead?
A: Polymorphism has vtable lookup, std::function has heap allocation overhead. Function pointers are the fastest.
Q5: How do I set the basic Strategy?
A: Set the default Strategy in the constructor.
Q6: What are Strategy Pattern learning resources?
A:
- “Design Patterns” by Gang of Four
- “Head First Design Patterns” by Freeman & Freeman
- Refactoring Guru: Strategy Pattern
One line summary: Strategy Pattern allows you to encapsulate algorithms and replace them at runtime. Next, it would be a good idea to read Command Pattern.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ virtual function | “Virtual Functions” guide
- Complete Guide to C++ Observer Pattern | Event-based architecture and signals/slots
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented?
Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, strategy, pattern, algorithm, polymorphism, lambda, etc.
Related articles
- C++ CRTP Complete Guide | Static polymorphism and compile-time optimization
- C++ Factory Pattern Complete Guide | Object creation encapsulation and extensibility
- C++ Visitor Pattern |
- Arrays and lists | Complete summary of essential data structures for coding tests
- C++ Adapter Pattern Complete Guide | Interface conversion and compatibility