(E)numerierte Klassen?



  • Hi,

    wie der Titel schon sagt, suche ich eine möglichkeit, Klassen durchzunumerieren. Ich meine also nicht ein Array von Objekten, sondern eine Art Array von Klassen - oder eher eine durchnumerierte Sammlung.

    Wozu? Ich habe einen Netzwerk-Code, bei dem das erste byte darüber entscheidet, um welchen Paket-Type es sich handelt, und dann den stream in die jeweilige Klasse einliest.

    class Pack1 : public PacketBase ...
    class Pack2 : public PacketBase ...
    
    PacketBase* pb;
    switch(byte1)
    {
     case PACK_TYPE_1:
      pb = new Pack1;
     break;
     case PACK_TYPE_2:
      pb = new Pack2;
     break;
     //...
    }
    

    Diese switch-Schleife verbraucht viel Platz und macht mir auch sonst einen fehleranfälligen Eindruck. Viel einfacher ginge es doch so:

    class Pack[PACK_TYPE_1] : public PacketBase ...
    class Pack[PACK_TYPE_2] : public PacketBase ...
    
    PacketBase* pb = new Pack[byte1];
    

    Mir ist klar, dass das mit Arrays aus verschiedenen Gründen überhaupt nicht ginge. Vielleicht kann man aber meine Klassen mithilfe von Templates durchnumerieren oder gibt es eine andere sinnvolle Möglichkeit? Kann man das switch irgendwie sinnvoll umgehen?

    Mfg,
    voidpointer



  • man kann per präprozessor direktiven arbeiten

    der befehl ## hängt ja irgendwie den quellcode zusammen
    http://de.wikibooks.org/wiki/C%2B%2B-Programmierung:_Weitere_Grundelemente:_Vorarbeiter_des_Compilers#.23



  • Spontan hätte ich jetzt gesagt mit template <int i> class Pack - aber das geht in deinem Fall leider nicht, weil das Templateargument zur Compilezeit feststehen muss.
    Um die eine oder andere Art Fallunterscheidung kommst du wohl nicht drum herum. Du kannst natürlich das Ganze in einer Funktion kapseln:

    PacketBase* createPacket(int n)
    {
      switch (n)
      case 1: return new Packet1;
      case 2: return new Packet2;
      default: /* FEHLER */
    }
    


  • #include <map>
    
    class PacketBase
    {
    };
    
    class Packet1 : public PacketBase
    {
    };
    
    class Packet2 : public PacketBase
    {
    };
    
    typedef PacketBase* (*PacketCreater)();
    
    class Creater
    {
       public:
          PacketBase* Create(int index) const;
          void Register(int index, PacketCreater func);
       private:
          std::map<int, PacketCreater> creaters;
    };
    
    PacketBase* Creater::Create(int index) const
    {
       std::map<int, PacketCreater>::const_iterator it = creaters.find(index);
       if(it != creaters.end())
       {
          return (it->second)();
       }
       else
       {
          return 0;
       }
    }
    
    void Creater::Register(int index, PacketCreater func)
    {
       creaters[index] = func;
    }
    
    template<typename T>
    PacketBase* CreateNew()
    {
       return new T;
    }
    
    int main()
    {
       Creater myCreater;
       myCreater.Register(1, CreateNew<Packet1>);
       myCreater.Register(2, CreateNew<Packet2>);
       //...
    }
    

    Du könntest eine kleine Factory (so ähnlich wie oben) entwerfen... Ist aber in deinem Fall vielleicht ein wenig übertrieben. Aber eine bessere Lösung als das und was bisher gesagt wurde fällt mir auch nicht ein 😞

    Felix



  • Tjaaaa, da sind wir insgesamt wohl wieder beim klassischen Thema "C++ ist eine Sprache mit statischer Typisierung" angekommen - und deswegen "geht das" nicht wirklich (von Grund auf erst zur Laufzeit bestimmen, was man denn mit den reinkommenden Daten machen möchte).

    :p

    Ich würde auch mit Phoemux' Ansatz loslegen ... allerdings kann man da vielleicht noch ein wenig sparen, indem man aus Paket1-n ein template<int> macht; aber das bringt nur etwas, wenn man genügend Überschneidungen zwischen den einzelnen Pakt-Typen hat.

    Gruß,

    Simon2.



  • Das ist doch ein klasse Fall für Prototypes. Da man die Typen zur Laufzeit nicht bekommt, benutzt man halt nicht konventionelle OOP sondern Prototyp-OOP: Die verschiedenen Klassen werden in einem Array untergebracht, das so aussieht:

    vector<PacketBase*> prototypes;
    prototypes.push_back(new Packet1());
    prototypes.push_back(new Packet2());
    // …
    

    Statt einer Instanzierung wird dann dieser Prototyp geklont:

    PacketBase* pb = prototypes[byte1]->clone();
    

    … die `clone`-Routine muss dann natürlich entsprechend implementiert werden, sollte aber nicht allzu schwer sein.



  • hmm grobe Idee wie man das mit den Prototypes usw. verallgemeinern könnte als templategschichte mit dem index als template-Parameter:
    (nur mal so hingeferkelt)

    template <class Base>
    class PrototypeFactory
    {
    public:
      virtual ~PrototypeFactory() = 0
      {
        for (std::map<int,Base*>::iterator pos = ptmap.begin(); pos != ptmap.end(); ++pos)
        {
          delete pos->second;
        }
      }
    
      static Base* create(int index)
      {
        std::map<int,Base*>::iterator pos = ptmap.find(index);
        if (pos == ptmap.end() ) 
        {
          throw std::out_of_range();
        }
        return pos->second->clone();
      }
    
      virtual Base* clone() = 0; //Alle abgeleiteten Klassen müssen Klonbar sein...
    
      template <class Derived, int Index>
      friend class RegisterPrototype<Base, Derived, Index>::Init
    private:
      static std::map<int, Base*> ptmap;
    };
    
    template <class Base, class Derived, int N>
    class RegisterPrototype : public Base
    {
    public:
      friend class PrototypeFactory<Base>; //weiss nicht ob das noetig ist damit PF<Base> Init als friend deklarieren kann
    
      static Base* create() {return create(N);}
    
    private:
      class Init
      {
        Init()
        {
          PrototypeFactory<Base>::ptmap[N] = new Derived();
        }
      }
      const static Init i;
    };
    template <class Base, class Derived, int N>
    const RegisterPrototype<Base,Derived,N>::Init i;  //Definition des statischen Members
    

    Benutzung:

    class PacketBase : public PrototypeFactory<PacketBase>
    { /*...*/ };
    
    template<int N>
    class Packet : public RegisterPrototype<PacketBase, Packet<N>, N>
    { /*...*/ };
    

    Eine Instantiierung (z.B. Packet<13>) erbt indirekt von PacketBase, durch die Initialisierung des statischen Members von RegisterPrototype<PacketBase,Packet<13>,13> wird automatisch ein Objekt von Packet<13> in der Map der Prototype-Factory abgelegt.

    Ich habs ehrlich gesagt nicht ausprobiert, vielleicht hat ja jemand die Muße - oder kann mir gleich sagen warums nicht klappen kann.
    Evtl. müsste die map nach Singleton-Muster in eine mapinstance()-Methode gepackt werden um Problemen bei der Initialisierungsreihenfolge aus dem Weg zu gehn.



  • Vielen Dank, nun habe ich mal einen kleinen Einblick bekommen. Das mit der map scheint mir eine sinnvolle Idee (hat nur den Nachteil, dass man erst mal 100 Pakete initialisieren muss 😞 - aber trotzdem gut)


Anmelden zum Antworten