Design: Policy-Based für "Source-Code-Vorlage"? Kritk/Anregungen?



  • Hi zusammen,

    ich würde gerne zu Lernzwecken (und weils dann auch praktisch nutzbar ist) eine Art "Source-Code-Vorlage" für neue Projekte machen, welche mehr oder weniger häufig vorkommende Aufgaben schon löst.
    Solche Aufgaben sind z.B. Kommandozeilenoptionen festzulegen, logging, signal-handling einrichten, einen Threadpool anlegen oder vielleicht ein kleines WebInterface.
    Ansonsten kommen noch cmake-Files usw. dazu.
    Wenn man jetzt eine Idee umsetzen oder ein neues Projekt anfangen möchte kann man sich das "Vorlagen-Verzeichnis" einfach kopieren und sofort loslegen mit der eigentlichen Idee.

    Ich hab meinen ersten Versuch mal kurz zusammen gefasst:

    ///////////////////////////////////////////////////////////////////////////////
    ///
    /// Framework-intern
    template <class CmdLineOptionsPolicy,
              class WorkerPolicy,
              class InterfacePolicy>
    class frame
    {
    public:
        frame() {}
        int run() {
            init_stuff();
            return real_main();
        }
    
    protected:
        virtual int real_main();
        CmdLineOptionsPolicy cmd_line;
        WorkerPolicy worker_;
        InterfacePolicy interface_;
    
    private:
        init_stuff() {
            /* Setup stuff like logging, command-line options and so on */
            cmd_line.addOption("h","display help");
        }
    };
    
    int main()
    {
        user_main um;
        return um.run();
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    ///
    /// Mit dieser Klasse wird letztendlich gearbeitet
    class user_main : public frame<simple_cmd_options, asio_ios_pool, no_interface>
    {
    public:
        user_main() {
            cmd_line.addOption("s","makes program do something");
        }
    private:
        int real_main()  // Hier landet dann der eigentliche Programmcode
        {
            if(cmd_line.getOption("s"))
                doSomethingNice();
            undsoweiter();
            return 0;
        }
    }
    

    Vorteilhaft ist für mich jetzt hier dass ich durch Austauschen der Klassen für die "Policies" schnell ändern kann was das Programm letztendlich braucht.
    Am Beispiel der CmdLineOptions hab ich dann verschiedene Klassen (z.B. no_cmd_line_options, simple_cmd_line_options, boost_cmd_line_options), welche zumindest immer die Funktionen setOption() und getOption() implementieren (oder bei no_cmd_line_options eben als leere Methode umsetzen).

    Da ich das ganze ja vor allem zu Lernzwecken machen möchte kommen mir ein paar Fragen, vor allem zum Design:

    • Ist der Ansatz das in Form von "Policy-Based design" umzusetzen sinnvoll?
    • Wie könnte ich es umsetzen wenn ich zwei Policies nutzen möchte (z.B. zwei Interfaces, Web und Shell)?
    • Ich hätte gedacht dass unbenutze Klassen aus der Binary rausfliegen und ich somit keinen Overhead habe (g++ mit -O3). Allerdings scheint das aber nicht zu klappen. Lässt sich das, außer mit großem Präprozessor-Direktiven-Aufwand optimieren?
    • Wenn der Designansatz so i.O. ist: Wie könnte ich das noch verschönern, optimieren, mehr C++11 nutzen, übersichtlicher gestalten, usw? Geht ja schließlich um den Lerneffekt.


  • Was hast du da für einen Vorteil?

    Ich meine, du schiebst was in die Hilfsklasse rein, kommst in der erbenden Klasse wieder dran und naja, mehr nicht. Prüfungen oder Checks gibt es keine.

    Die Kommandozeilen Optionen sind so wie ich das bisher verstehe nicht wirklich gut, da Flexibilität fehlt und letztlich nur auf String Basis gearbeitet wird. Das heisst, das Parsen musst du dann doch wieder übernehmen.

    Policy-Based-Design ist eigentlich dasselbe wie Vererbung, nur halt zur Compilerzeit statt zur Laufzeit



  • Naja, frame macht schon ein bisschen mehr, ich hab das nur der Übersichtlichkeit halber rausgelassen.

    Konkret wären das z.B. das Initialisieren vom Logging, wenn vorhanden weiterleiten von Kommandozeilenoptionen an die Policiy-Klassen, Bereitstellen von Standard-Kommandozeilenoptionen (help, version, loglevel), Exception-Handling.

    Deine Kritik bez. der Kommandozeilen-Optionen versteh ich, allerdings sind 80% der genutzten Optionen letztendlich doch strings und wenn ich es doch mal mehr "fine-grained" brauche kann ich ja z.B. eine policy für z.B. boost-program-options nehmen welche mir dann wirklich mehr möglichkeiten bietet.

    Findest du das Ganze jetzt grundsätzlich falsch oder einfach nur den Ansatz über das policy-based-design?



  • Naja, so wie ich das hier sehe, bringt dir das nicht so viele Vorteile. Du hast eine Klasse die ein paar Kleinigkeiten macht, mehr aber auch nicht. Willst etwa Kommandzeilen Optionen nutzen, musst du die wie gesagt, dann doch wieder komplett selbst implementieren. Ergo, die Arbeit hier wäre dann fast für die Katz.

    Genauso sieht das mit dem Logging aus. Klar eine gute Logging Funktionalität ist super, aber dann schreib dir die und wzar nicht in eine monolithische Klasse wie du es hier vorhast. (Immer dran denken, Funktionalität von Funktionen/Klassen sollten orthogonal sein -> eine Funktion/Klasse = eine klare Aufgabe)



  • So eine Frame Klasse macht für mich überhaupt keinen Sinn. Funktionalität sollte man trennen. Wenn du eine gute Logging Bibliothek hast, kannst du die auch problemlos benutzen und musst sie nicht als Basisklasse für deine "Hauptklasse" benutzen. Außerdem gibts schon Logging Bibliotheken, vielleicht sollte man das Rad nicht jedesmal neuerfinden. Ich kenne zwar keine wirklich guten C++ Logging Frameworks, die keine Probleme hätten, aber die sind auf jeden Fall deutlich umfangreicher und flexibler als das was du dir anscheinend vorstellst.



  • Hi,
    vielleicht hab ich mich schlecht ausgedrückt, frame implementiert selbst kein logging oder sonstwas, es kümmert sich nur um die Initialisierung und das Zusammenspiel der ganzen Sachen.
    Fürs logging nehme ich boost.log, für die Parameter je nachdem wie komplex ich es brauche entweder boost.programoptions oder meine selbstgeschriebene Klasse simple_cmd_line_options die nix macht als argv in strings in eine std::map zu legen.
    Diese sachen kommen dann über die "policy" in frame rein.

    Der Sinn hinter frame sollte eigentlich nur sein dass ich mir immer wiederkehrende Arbeiten (Kleinigkeiten) erspare, welche bei fast jedem Projekt dabei sind.

    Vielleicht ist der Weg das über dieses Policy-Based-Design zu machen nicht ganz richtig, das hab ich jetzt schon rausgehört.

    Welcher Ansatz wäre denn sinnvoll?



  • ebpp schrieb:

    frame implementiert selbst kein logging oder sonstwas, es kümmert sich nur um die Initialisierung und das Zusammenspiel der ganzen Sachen.

    Ja, das hab ich schon verstanden. Aber ich seh darin trotzdem überhaupt keinen Sinn. Das ist etwas, was du in deinem Programm wahrscheinlich nur ein mal brauchst, das ist absolut kein Anwendungsfall für Policy Based Design.



  • Davon abgesehen brauchst du dir bei policies nicht noch virtuelle Funktionen ans Bein binden.

    #include <iostream>
    
    struct PolicyA
    {
      void policyFun() { std::cout << "Policy A\n"; }
    };
    
    struct PolicyB
    {
      void policyFun() { std::cout << "Policy B\n"; }
    };
    
    template <typename Policy>
    struct PolicyAdapter : Policy
    {
      void indirectUse()
      {
        Policy::policyFun();
      }
    };
    
    typedef PolicyAdapter<PolicyA> my_config;
    
    int main()
    {
      my_config foo;
    
      foo.policyFun();
      foo.indirectUse();
      return 0;
    }
    

Anmelden zum Antworten