2015-03-13 4 views
5

implementiere Ich interessiere mich für den besten Weg, um das Strategie-Muster in C++ zu implementieren. Bis jetzt habe ich früher immer den Standard-Weg, wo der Kontext einen Zeiger auf die Basisstrategie Klasse wie folgt:Wie man Strategiemuster in C++ mit std :: function

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_; 

Da Zeiger auf Objekte, die in einem schlechten Verhalten führen kann, ich war auf der Suche für eine sicherere Weise, dieses Muster zu implementieren, und ich fand this question, wo std::function vorgeschlagen werden, als eine bessere Möglichkeit, dieses Muster zu behandeln.

Könnte jemand bitte besser erklären, wie std::function funktioniert, vielleicht mit einem Beispiel mit dem Strategie-Muster?

Antwort

7

Beachten Sie, dass Objekte mit einer Methode isomorph zu Funktionen sind und Strategien nur Objekte mit einer einzigen Methode sind.

Also im Grunde erhalten Sie alle Ihre Klassen los, und Sie std::function<void()> nur statt:

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

    void run() { strategy(); } 

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

Dann können Sie jede aufrufbare an den Konstruktor Context passieren:

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

In diesem Fall, wie würden Sie F (die Strategie) implementieren? Und warum müssen Sie std :: move verwenden? – gcswoosh

+2

@Gabrielecswoosh Die Strategie ist nur irgendein Funktionszeiger oder Objekt mit 'operator()' überladen.Mein Beispiel übergibt ein Lambda (definiert "void operator()() const"). 'std :: move' soll eine Kopie verhindern. – rightfold

0

So etwas wie Dies ?

#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); 
} 

Ich meine, Strategie-Muster ist eine Lösung für den Mangel, keine tatsächlichen Lambdas zu haben. Das ist gelöst, also könntest du einfach die entsprechende Funktion übergeben.

3

Es gibt ein wenig Diskussion zu diesem Thema here und here. Ich denke, es hängt vom jeweiligen Fall ab. Ist Ihre Strategie zum Beispiel nur ein einfacher Funktionsaufruf? Ich habe oft Strategie-Muster, in denen meine Strategie mehrere Fähigkeiten benötigt, die nur durch eine Funktion oder einen Funktor nicht gut gehandhabt werden können. Aber wenn Sie nur eine Funktion oder einen Funktor benötigen, dann ist std::function eine praktische Möglichkeit, ultimative Flexibilität zu ermöglichen, Funktionszeiger, Lambdas oder Funktoren zu speichern. Es kann zu Leistungsproblemen kommen, die für die ursprüngliche Boost-Implementierung here diskutiert wurden.

+0

Was würden Sie in dem Fall vorschlagen, in dem die Strategie nicht einfach ein Funktionsaufruf ist? In diesem Fall ist ein Zeiger auf eine abstrakte Klasse der einzige Weg? – gcswoosh

+1

Ja. Sie können die richtige Ressourcenkontrolle mit 'std :: unique_ptr' oder' std :: shared_ptr' umgehen. – sfjac

1

Arbeiten an der Antwort von райтфолд

Grundsätzlich Sie alle Ihre Klassen loszuwerden, und Sie nur statt std :: Funktion verwenden.

Diese verallgemeinerte Funktion ermöglicht es, Funktionen zu übergeben, Lambda, functors und Member-Funktionen (mit std :: bind)

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

void run() { strategy(); } 

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

Dann können Sie jede aufrufbare an den Konstruktor von Context übergeben:

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

oder

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


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

oder

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

int main(){ 
    SayHelloWorld hello_world; 
    Context ctx(hello_world); 
    ctx.run(); 
} 
Verwandte Themen