C++ Strategy Pattern: Complete Guide | Algorithms, Lambdas & std::function

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

  1. Basic structure (polymorphic)
  2. Function pointer method
  3. Lambda method
  4. std::function method
  5. Frequently occurring problems and solutions
  6. Production Patterns
  7. Complete example: Compression algorithm
  8. 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

methodAdvantagesDisadvantages
polymorphismType safe, extensiblevtable overhead, heap allocation
Function pointerFast, simpleLack of type safety, no condition
LambdaInline, Capture CapableComplex type, difficult to debug
std::functionFlexible, all callableLarge overhead, heap allocation

organize

conceptDescription
Strategy PatternRuntime replacement by encapsulating algorithms
PurposeAlgorithm independence, scalability
StructureContext, Strategy, ConcreteStrategy
AdvantagesOCP compliance, removal of conditional statements, easy testing
Disadvantagesclass increment, indirect reference
Use CaseSorting, 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:

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.


  • 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