Treiberklasse für verschiedene Protokollklassen



  • Hallo zusammen

    ich habe einige Protokollklassen, die Datenblöcke verarbeiten, Header parsen, Messagetypen auswerten, Heartbeats senden und empfangen etc. Also eine Klasse, die grundlegende Funktionen einer Kommunikationsschicht implementiert.
    Aufgrund der Testbarkeit sind diese meist mit public Methoden ausgestattet und verzichten auf jede Art von Socket-Funktionalität oder Threads. Es sind Module die Eingangsdaten verarbeiten und ggfs. Ausgangsdaten produzieren. Das Betreiben von Sockets und Threads übernimmt dann die überladende Klasse, sprich die Ableitung.

    Minimal-Beispiel:

    class MyProtocol
    {
    public:
          void setHeartbeatConfig( uint32_t interval, uint32_t timeout_interval );
          void receiveDataBlock( const char *data, std::size_t datalength );
          void checkHeartbeat();
    
    protected:
         virtual void sendDataBlock( const char *, std::size_t ) = 0;
    };
    

    Nun habe ich mittlerweile mehrere Protokolle im Bestand und zu jedem Protokoll wiederum eine "Treiber-klasse" die einen Socket und einen Thread erzeugt und die Protokollklasse "betreibt".

    Da ich gerade etwas aufräume, hatte ich den Gedanken, nur eine einzige Treiberklasse als Template zu bauen, die als Typ dann die jeweilige Protokollklasse aufnimmt, und zwar in der Form:

    template <class T> 
    class ProtocolDriver : public T
    {
    protected:
            void readSocket()
            {
               // Diverser Code der Sockets ausliest
                T::receiveDataBlock( data, datalength );
            }
    
    
           void configure()
          {
               T::setHeartbeatConfig( 30s, 60s );
               // ....
          }
    
          void sendDataBlock( const char *, std::size_t ) override
          {
               // Sendet Datenblock via Socket
          }
    
          //  ....
        
    };
    

    Sprich, das Klassentemplate leitet sich von T ab.
    D.h. alle Protokollklassen müssen das gleiche Interface aufweisen.
    Das Klassentemplate soll grundlegende Socket und Threadmechaniken übernehmen, ggfs. Timer erzeugen, um z.B. die Heartbeats zu überwachen und was noch so dazu gehört.

    Ziel ist es, dass der Benutzer dann von diesem Klassentemplate ableitet, mit dem Protokoll seiner Wahl und zusätzlich noch die Möglichkeit hat, gezielt virtuelle Methoden seines Protokolls zu überladen, an denen z.B. das Treiber-Template gar ein Interesse hat.
    Ich erhoffe mir dadurch genug Freiheiten für den Benutzer, aber trotzdem wenig Aufwand mit dem ganzen Drumherum ( Socket, Thread ).

    Die Protokollklassen sollen dabei keine zwanghaften Abhängigkeiten haben. Einzige Bedingung ist, dass sie die erforderlichen Interface-Methoden mitbringen und auch notwendige abstrakte Methoden, zum Beispiel zum Abfragen von Konfigurationsparametern.

    Schwierigkeiten bereitet mir aktuell die Konfiguration des Protokolls, da jedes Protokoll bestimmte Parameter benötigt, die für den Betrieb des Protokolls notwendig sind.
    Dazu muss das Protokoll drei abstrakte Methoden bereitstellen:

    virtual bool getConfigEntry( const std::string &, bool & ) = 0;
    virtual bool getConfigEntry( const std::string &, uint32_t & ) = 0;
     virtual bool getConfigEntry( const std::string &, std::string & ) = 0;
    

    Das fühlt sich irgendwie falsch an.
    Aktuell habe ich zum ersten Mal das Gefühl, dass ich versuche eine Aufgabe in ein Korsett zu pressen, in dass sie nicht passt, nur weil ich von

    template <class T>
    class FooBar : public T
    {};
    

    besessen bin.

    Was sagt ihr dazu? Habe ich mich hier verrannt?



  • Hallo @It0101 ,
    klingt zumindest erst mal gut. Wenn ich Dich richtig verstehe, werden "Datenblöcke" verarbeitet, und mit verschiedenen "Protokollen transparent weiter verarbeitet. Also entweder mit TCP/IP oder RS 232 oder...#
    Zu Deiner Frage mit den Parametern fällt mir eine abstrakte Parameter-Klasse ein, die "ihre" Parameter für ein bestimmtes Protokoll mit sich führt. Also mit der abgeleiteten Protokoll-Klasse eine abgeleitete Parameter-Klasse.



  • @Helmut-Jakoby sagte in Treiberklasse für verschiedene Protokollklassen:

    klingt zumindest erst mal gut. Wenn ich Dich richtig verstehe, werden "Datenblöcke" verarbeitet, und mit verschiedenen "Protokollen transparent weiter verarbeitet. Also entweder mit TCP/IP oder RS 232 oder...#

    Ja genau. Es sind Daten die mittels Socket via TCP empfangen/gesendet werden.

    Zu Deiner Frage mit den Parametern fällt mir eine abstrakte Parameter-Klasse ein, die "ihre" Parameter für ein bestimmtes Protokoll mit sich führt. Also mit der abgeleiteten Protokoll-Klasse eine abgeleitete Parameter-Klasse.

    Du meinst, so in der Art:

    template <class T, class P> 
    class ProtocolDriver : public T
    {
    protected: 
        void setProperties( const T &props_ ) { T::setProperties( props_  ); }
    };
    

    Wenn ich generalisieren will ( über mehrere unterschiedliche Protokolle ), und in meiner Treiberklasse auch Arbeit übernehmen will und sie nicht nur als Wrapper nutzen will, muss ich aber auch auf einige Properties zugreifen können und da müsste dann wiederum die Properties-Class ein teilweise einheitliches Interface haben, damit der Treiber darauf zugreifen kann..
    Beispiel: einen Timer für die Heartbeat-Kontrolle erzeugen. Wozu ich wiederum das Heartbeatinterval kennnen muss, was ich dann in dieser Propertiesklasse implementieren würde. Klingt für mich, als würde ich die Problematik nur verlagern.



  • Hi zusammen

    folgendes Problem hat sich ergeben.
    Minimalbeispiel:

    #include <iostream>
    
    class Base
    {
    public:
        virtual void call()
        {
            std::cout << "Base::call" << std::endl;
        }
    };
    
    template <class T>
    class Driver : public T
    {
    public:
        void call_test()
        {
            T::call();
        }
    };
    
    class Derived : public Driver<Base>
    {
    public:
        void call() override
        {
            std::cout << "Derived::call" << std::endl;
        }
    };
    
    
    int main()
    {
        Derived foo;
        foo.call_test();
        return 0;
    }
    

    Ich hatte erwartet, dass ich von meinem Driver ableiten kann, zusätzlich die virtuellen Methoden von Base überladen kann, und die überladenen Methoden aufgerufen werden.
    Scheinbar wird hier aber nur die Base-Methode aufgerufen, vermutlich weil ich sie im Driver direkt mit Base::call() aufrufe. ( T::call() ).

    Gibt es hier eine Möglichkeit die Methode "call" im Driver aufzurufen, so dass überladene Methoden ebenfalls aufgerufen werden?



  • Nevermind, es funzt auf die Art:

            auto casted = dynamic_cast<T*>( this );
            if ( casted )
                casted->call();
    

    Gefällt mir zwar nicht so gut, aber funzt zumindest.

    Falls jemand noch eine elegantere Art ohne Cast kennt, lasset es mich wissen 😉



  • Mit "überladen" meinst du stets "überschreiben" oder?
    Kann/sollte nicht Derived::call pauschal das Base::call aufrufen?



  • @Jockelx sagte in Treiberklasse für verschiedene Protokollklassen:

    Mit "überladen" meinst du stets "überschreiben" oder?
    Kann/sollte nicht Derived::call pauschal das Base::call aufrufen?

    ja, aber der Driver ruft explizit "Base::call" auf. Derived::call wird daher gar nicht gerufen.

    Ja, ich meine "Überschreiben"... Ich verwende sonst nur englisch, weiß daher nicht so genau wie man "override" übersetzen würde 😃



  • @It0101 sagte in Treiberklasse für verschiedene Protokollklassen:

    ja, aber der Driver ruft explizit "Base::call" auf. Derived::call wird daher gar nicht gerufen.

    Ja, aber muss das denn?

    #include <iostream>
    
    class Base
    {
    public:
        virtual void call()
        {
            std::cout << "Base::call" << std::endl;
        }
    };
    
    template <class T>
    class Driver : public T
    {
    public:
        void call_test()
        {
            this->call();
        }
    };
    
    class Derived : public Driver<Base>
    {
    public:
        void call() override
        {
            Base::call();
            std::cout << "Derived::call" << std::endl;
        }
    };
    
    
    int main()
    {
        Derived foo;
        foo.call_test();
        return 0;
    }
    

    Output:
    Base::call
    Derived::call



  • danke. Funzt. Irgendwie stand ich da auf dem Schlauch.


Anmelden zum Antworten