10

I'm reasing about the best way to implement the strategy pattern in C++. Up to now, I've always used the standard way, where the context has a pointer to the base strategy class as follows:

 class AbstractStrategy{
 public:
     virtual void exec() = 0;
 }
 class ConcreteStrategyA{
 public:
     void exec();
 }
 class ConcreteStrategyB{
 public:
     void exec();
 }

 class Context{
 public:
     Context(AbstractStrategy* strategy):strategy_(strategy){}
     ~Context(){
          delete strategy;
       }
      void run(){
           strategy->exec();
      }
 private:
     AbstractStrategy* strategy_;

Since having pointers to objects can result in bad behavior, I was looking for a safer way to implement this pattern and I found this question where std::function are proposed as a better way to handle this pattern.

Could someone please explain better how std::function works, maybe with an example with the strategy pattern?

Community
  • 1
  • 1
gcswoosh
  • 1,279
  • 1
  • 15
  • 31

4 Answers4

16

Note that single-method objects are isomorphic to functions, and strategies are just single-method objects.

So basically, you get rid of all your classes, and you just use std::function<void()> instead:

class Context {
public:
    template<typename F>
    explicit Context(F strategy) : strategy(std::move(strategy)) { }

    void run() { strategy(); }

private:
    std::function<void()> strategy;
};

Then you can pass any callable to the constructor of Context:

Context ctx([] { std::cout << "Hello, world!\n"; });
ctx.run();
  • 1
    In this case, how would you implement F (the strategy)? And why you need to use std::move? – gcswoosh Mar 13 '15 at 12:13
  • 2
    @Gabrielecswoosh The strategy is just any function pointer or object with `operator()` overloaded. My example passes a lambda (which defines `void operator()() const`). `std::move` is to prevent a copy. –  Mar 13 '15 at 12:13
5

There's a bit of a discussion of this topic here and here. I think it depends on the particular case at hand. Is your strategy a simple function call only, for instance - I often have strategy patterns in which my strategy will need multiple capabilities, which isn't handled well by just having a function or functor. But if you do need just a function or functor, then std::function is a handy way to allow ultimate flexibility, storing function pointers, lambdas or functors. There can be performance issues, which were discussed here for the original boost implementation.

Community
  • 1
  • 1
sfjac
  • 7,119
  • 5
  • 45
  • 69
  • What would you suggest in the case where the strategy is not simple a function call? In that case a pointer to an abstract class is the only way? – gcswoosh Mar 13 '15 at 12:22
  • 1
    Yes. You can handle proper resource control with `std::unique_ptr` or `std::shared_ptr` as appropriate. – sfjac Mar 13 '15 at 12:23
2

Working on the answer of райтфолд

Basically, you get rid of all your classes, and you just use std::function instead.

This generalized function allows you to pass functions, lambda, functors, and member functions (using std::bind)

class Context {
public:
   explicit Context(std::function<void()> input) : strategy(input) { }

void run() { strategy(); }

private:
   std::function<void()> strategy;
};

Then you can pass any callable to the constructor of Context:

Context ctx([] { std::cout << "Hello, world!\n"; });
ctx.run();

or

void sayHelloWorld(){
   std::cout << "Hello, world!\n";
}


int main(){
   Context ctx( sayHelloWorld );
   ctx.run();  
}

or

class SayHelloWorld{
   operator()(){std::cout << "Hello, world!\n";}
}

int main(){
   SayHelloWorld hello_world;
   Context ctx( hello_world );
   ctx.run();  
}
Darius Zu CH
  • 145
  • 1
  • 8
0

Something like this ?

#include <functional>
#include <iostream>


typedef std::function<int(int)> Strategy;

void execute_strategy(Strategy strategy, int object) {
    std::cout << strategy(object) << std::endl;
};

int power2(int i) {
    return i*i;
};

int main() {
    execute_strategy(power2, 3);
}

I mean, strategy pattern is a solution for the shortcoming of not having actual lambdas. That's resolved, so you could just pass the appropriate function around.

Tasos Vogiatzoglou
  • 2,393
  • 12
  • 16