2015-06-02 10 views
6

Ich versuche zu verstehen, die echte Anforderung der Verwendung von Vorlagen für Policy-basierte Design. Beim Durcharbeiten der neuen Template-Designs in C++ fand ich heraus, dass das richtlinienbasierte Klassendesign eine sehr vorgeschlagene Art des Designs ist, die es ermöglicht, verschiedene Verhaltensweisen von Policy-Klassen "einzufügen". Ein minimales Beispiel ist die folgende (eine verkürzte Version des Wikis):Wann templated richtlinienbasiertes Design gegenüber nicht-templated Vererbung basierend Design

template <typename LanguagePolicy> 
class HelloWorld : private LanguagePolicy 
{ 
    using LanguagePolicy::message; 

public: 
    // Behaviour method 
    void run() const 
    { 
     // policy methods 
     cout << message(); 
    } 
}; 

class LanguagePolicyA 
{ 
protected: 
    std::string message() const 
    { 
     return "Hello, World!"; 
    } 
}; 
//usage 
HelloWorld<LanguagePolicyA> hello_worlda; 
hello_worlda.run(); // prints "Hello, World!" 

Eine schnelle Analyse zeigt, dass nur message() verschiedene steckbare Methoden erhalten wir von einem Templat-Typ, deren Definition erben kann von jedermann zur Verfügung gestellt werden (und zur Kompilierzeit identifiziert).

Aber das gleiche Maß an Abstraktion (und konfigurierbare Methoden) kann erreicht werden, ohne einen Vorlagencode zu verwenden und durch den einfachen Old-School-Laufzeitpolymorphismus wie unten gezeigt.

class HelloWorld 
{ 
    LanguagePolicy *lp; //list of all plugable class 
public: 
    HelloWorld(LanguagePolicy *lpn) { 
     lp = lpn; 
    } 

    // Behaviour method 
    void run() const 
    { 
     // policy methods 
     cout << lp->message(); 
    } 
}; 
class LanguagePolicy 
{ 
protected: 
    virtual std::string message() const; 
}; 

class LanguagePolicyA: LanguagePolicy 
{ 
protected: 
    std::string message() const 
    { 
     return "Hello, World!"; 
    } 
}; 
//usage 
HelloWorld helloworld(new LanguagePolicyA); 
helloworld.run(); 

Funktionalität und Abstraktionsebene weise ich sehe nicht viel Unterschied in den beiden Ansatz (obwohl der zweite Ansatz paar zusätzliche Zeilen Code hat für LanguagePolicy, ich denke, dass es für die anderen Benutzer benötigt wird, die Schnittstelle zu kennen, sonst ist das Verständnis LanguagePolicy von der Dokumentation abhängig). Aber ich denke, dass das später "sauber" sein wird (kommt von jemandem, der die Vorlage nicht oft benutzt hat). Dies liegt daran, dass meiner Meinung nach nicht-templatierte Klassen sauberer anzusehen und zu verstehen sind. Ein sehr gutes Beispiel ist die beliebte Bibliothek VTK (Visualization Tool Kit), die mit dem zweiten Ansatz viele verschiedene Probleme löst. Obwohl es nicht umfangreiche Dokumentationen von VTK gibt, können die meisten von uns - ihre Benutzer - einfach in ihre Klassendiagramme schauen (manchmal sind sie ziemlich groß) und Verhaltensweisen von Klassen ableiten; und entwickeln hochkonfigurierbare und komplizierte Pipelines in unserer Anwendung (VTK kann nicht als Template-basiert abgebildet werden :)). Das Gegenteil sind Bibliotheken wie STL/BOOST, von denen ich glaube, dass niemand in der Lage ist, die Arbeit der Klassen ohne die Verwendung umfangreicher Dokumentation zu identifizieren.

Also meine Frage ist, ist das Template-basierte Policy-Design wirklich überlegen (nur in diesem Szenario der Policy-basierte Design) als virtuelle Vererbung basiert? Wenn ja, wann und warum?

Antwort

1

Beide sind gültige Möglichkeiten der Strukturierung, es hängt tatsächlich von den Anforderungen ab. Z.B.

Laufzeit vs kompilieren Zeit Polymorphismus.

Wann willst/kannst/musst du Polymorphie erreichen?

Performance-Overhead der virtuellen Anrufe

Vorlagen erzeugen Code, der

Die tatsächliche Nutzung der Klasse keine Indirekt hat.

Wenn Sie heterogene Sammlungen speichern müssen, wird eine Basisklasse benötigt, daher müssen Sie die Vererbung verwenden.

Ein sehr gutes Buch über richtlinienbasierte Design (etwas veraltet, aber gut trotzdem) ist Modern C++ Design

+0

-> Vorlage 'simuliert' Laufzeit Polymerphism durch Generieren von Klassen zur Kompilierzeit. Also, warum nicht direkt Runtime verwenden? Warum kümmern wir uns? -> Ich werde einen Blick werfen, aber wie hoch ist der Aufwand für virtuelle Anrufe wirklich? Sind die signifikant genug, um einen Effekt zu verursachen? – krips89

+0

@ krips89 Der zweite Punkt. Bei virtuellen Aufrufen tritt ein Leistungsaufwand auf, der in einigen Fällen wichtig sein kann. –

+0

@ krips89 Es kommt aber darauf an, wenn man die Indirection, das Durcheinander mit den Cache-Zeilen, die Möglichkeit einer Template-Funktion berücksichtigt, kann es einen Gewinn aus der Verwendung von Policies geben. –

1

hängt von der Situation Ich denke, ...Eine mögliche Nachteil Vorlagen zu verwenden, ist, dass die Art zum Zeitpunkt der Kompilierung bekannt sein sollte:

HelloWorld<English> hw; // English is plugged at compile-time 

In Ihrem zweiten Beispiel, in dem Sie eine Zeiger-Basis verwenden, könnte dieser Zeiger auf eine Vielzahl von Punkt abgeleitete Klassen. Worauf genau es hinweist, muss zur Kompilierzeit nicht bekannt sein und kann daher zur Laufzeit durch (Benutzer-) Eingabe festgelegt werden. Ein möglicher Nachteil dieses Ansatzes ist der virtuelle Anruf-Overhead. In einigen Anwendungen und auf einigen Plattformen ist dies möglicherweise unerwünscht.

+0

Ja; Soweit ich das bisher verstanden habe, ist das einzige Problem, das beim zweiten Ansatz auftaucht, der "virtuelle Anruf-Overhead". Wenn ich rein in die Designperspektive schaue (ohne sich um den Implementierungsaufwand zu kümmern), ist der zweite Ansatz überlegen. – krips89

+0

Das ist der einzige Nachteil, an den ich gerade denken kann. Darüber hinaus helfen abstrakte Grundlagen, eine Schnittstelle für abgeleitete Klassen zu erzwingen, was schön ist. Statische Interface-Überprüfung wird wahrscheinlich in C++ 17 verfügbar sein, wenn Konzepte der Sprache hinzugefügt werden (genial). – JorenHeit