ifdefs über virtuelle subklassen auslagern?



  • danke erneut. Ich denke darüber nach und mir scheint es auch plausibler über templates zu gehen da ich ja bereits zur compilezeit weiß was los ist. Ich denke ich werde mich mit dem template-ansatz begnügnen...

    ich versuche noch gründe zu finden die für den template-ansatz sprechen...

    Wer hält dich ab nicht zur Laufzeit eine andere Zuweisung zu machen, wenn das nicht sein soll?

    niemand - das ist wohl das was du meintest oder?

    Neben performance suche ich noch weitere vorteile des template-ansatzes...

    danke für die hilfe



  • Du solltest das anderst anschauen.

    Du hast 2 Möglichkeiten.
    1. Virtuelle Funktionen usw. und
    2. Templates.

    Grob gesagt:
    1. Spricht für Laufzeitverhalten
    2. Wird benutzt, wenn Polymorphie nicht zur Laufzeit, sondern während der Kompilierung statt findet.

    Also fällt die intuitive Wahl in deinem Problem auf die 2, also templates.
    Jetzt solltest du Punkte suchen, warum Laufzeit Polymorphie gebraucht werden sollte.

    Du hast einen Hang zur Laufzeitpolymorphie. Warum weiss ich nicht, aber ich vermute du kommst von einer anderen Sprache, wo du das so lösen würdest. Aber du darfst da jetzt nicht einfach mal das, was du bereits kennst bevorzugen, weil das so in einer anderen Sprache gelöst würde. Ich sehe keinen Grund, warum du hier Laufzeitpolymorphie brauchen solltest, du etwa?

    Und um das klar zu stellen ich bevorzuge templates nicht wegen eines möglichen Geschwindigkeitsvorteiles, sondern weil sie einfach mehr Sinn machen. Wenn ich irgendwo Laufzeitpolymorphie sehe, dann gehe ich davon aus, dass ich die auch benutzen kann/soll, was in deinem Beispiel nicht der Fall ist. Wenn ich ein template sehe, dann weiss ich sofort, dass da die Auswahl nicht zur Laufzeit geschen soll, sondern zur Kompilierzeit.

    Ich denke ich werde mich mit dem template-ansatz begnügnen...

    Das klingt irgendwie so, als würdest du templates nicht gerne benutzen und sie eher vermieden werden sollen und das jeder andere Ansatz wahrscheinlich besser ist. Ich empfinde templates als sehr mächtiges Werkzeut, welches ich sogar lieber einsetze (wenn möglich), als Laufzeitpolymorphie. Templates verlangen keine so hohe Kopplung der Klasse mit einem Basistypen, damit die Polymorphie klappt und das ist einer der Punkte, was ich sehr schätze.



  • Danke für die ausführliche Hilfe und Beschreibung.
    Ich hoffe es ist kein falscher Eindruck entstanden - ich benutze Templates sogar gern wenn es denn sinnvoll ist. In diesem neuen Zusammenhang sehe ich (jetzt) sogar dass es sinnvoll wird. In diesem Sinne möchte ich für die Hinweise danken!

    Wie sieht es nun mit folgendem aus: Ich möchte 2 Vektoren abhängig vom user-input entweder mit double-werten oder mit complexen (2 doubles pro element) füllen. dies z.B. über ein einfaches inputargument.
    Zusätzlich soll anschließend je nach typ dann ausgelesen werden z.B. also template-spezifisch. Aber später dann auch templatebasiert für beide eingaben nur 1 code. wäre hier LaufzeitPolymorphie sinnvoller als dieser ansatz:

    Ist alles nur pseudocode und der sinn ist auch fraglich - es geht mir nur ums prinzip hier:

    //Vorsicht das ist nur pseudocode
    Vector<double>* vec_double = NULL;
    Vector<complex>* vec_complex = NULL;
    int main(int argc, int** argv)
    {
        // der eingabetyp wird aus argv ausgelesen und steht nun
       // in inputtype.
       if (inputtype == 0) 
       {
             vec_double = new Vector<double>(mysize);
             // Jetzt soll eine ausgabe kommen die in einer klasse steht
             Output out;
             out.printvector(vec_double); 
    
             //Jetzt kommt eine methode die templatebasiert in einer templatebasierten klasse ist:
             T_Class tc;
             double nbr = tc.nbrelements(vec_double);
        }
        else
       {
            vec_complex = new Vector<complex>(mysize);
            // Jetzt soll eine ausgabe kommen die in einer klasse steht 
              Output out;
             out.printvector(vec_double); 
    
             //Jetzt kommt eine methode die templatebasiert in einer templatebasierten klasse ist:
             T_Class tc;
             int nbr = tc.nbrelements(vec_double);
        }
        if(vec_double) delete vec_double;
        if(vec_complex) delete vec_complex;
    
    }
    
    class Output
    {
          public:
               void printvector(Vector<double> *vec)
               {
                     //angenommen die size ist bekannt
                    for (int i = 0; i < size; i++)
                    {
                          std::cout << vec[i] << std::endl;
                    }
                }
    
               void printvector(Vector<complex> *vec)
               {
                     //angenommen die size ist bekannt
                    for (int i = 0; i < size; i++)
                    {
                          std::cout << vec[i].real << vec[i].imag << std::endl;
                    }
                }
    }
    
    template <class T> class T_Class
    {
          public:
          int nbrelements (Vector<T>* vec_double);
    }
    
    template <class T> int 
    T_Class<T>::nbrelements(Vector<T>* vec)
    {
           nbr = 0;
           //Angenommen die size ist bekannt
           for (int i=0; i < size; i++)
               nbr += 1;
          return nbr;
    }
    


  • was spricht dagegen, complex<double> zu nutzen? den imaginärteil kann man ja auch 0 setzen^^

    bb

    edit:

    template <class T> int 
    T_Class<T>::nbrelements(Vector<T>* vec) 
    { 
           nbr = 0; 
           //Angenommen die size ist bekannt 
           for (int i=0; i < size; i++) 
               nbr += 1; 
          return nbr; 
    }
    

    ?
    vec->size() ?
    allg. find ich den code komisch...
    erst recht für nur pseudo-code...

    bb



  • 🙂 Ok - nehmen wir mal an man würde unbedingt zwei typen wollen - double und complex. Mir gehts hier nur um das prinzip bwzüglich templats und Laufzeitpolymoprhie...

    dass der code komisch ist hab ich ja schon geschrieben....

    Eine Zweite Frage die ich noch nicht verstehe:
    Bei dem template-Beispiel von Drakon wird ja sowohl die KLasse sequential als auch Parallel kompiliert.

    Ich will aber in den Methoden der Klasse Parallel Bibliotheksaufrufe benutzen die
    die Klasse Sequential nicht hat. Dazu inkludiere ich in der Klasse Parallel einfach die entsprechenden Header. Kompiliere ich mit meinem DParallel flag läuft alles. Die parallele version zu erstellen geht also. Kompiliere ich jedoch ohne flag meckert der Compiler dass er die Library-funktionen nicht kennt - klar - es wird auch mit nem anderen Compiler kompiliert (g++ statt wie im parallelen mit mpicxx z.B).

    Und wie kann ich das jetzt an meine Templates anpassen? Es sieht jetzt ganz danach aus als müsste ich jeden bibliotheksaufruf in der klasse Parallel in ein #ifndef packen...? Stimmt das? 😕



  • über kurze Hilfe würde ich mich freuen 🙂 ...



  • Warum so ungeduldig? Dein alter Post ist noch nicht einmal eine Stunde alt. 🙄



  • Warum so ungeduldig? Dein alter Post ist noch nicht einmal eine Stunde alt.

    falsch - er ist 1 Tag und 1 stunde alt. ungeduldig zwinged ja - weil ich vorwärtskommen will - und das liegt einzigund allein an mir ich weiß 🙂



  • Huch, in der Tat. Sorry dann, habe nur auf die Zeit geschaut. 🙂



  • Und wie kann ich das jetzt an meine Templates anpassen? Es sieht jetzt ganz danach aus als müsste ich jeden bibliotheksaufruf in der klasse Parallel in ein #ifndef packen...? Stimmt das?

    Ja. Du willst ja, dass der Compiler den Code gar nicht erst sieht und das kannst du ja nur vor dem Kompilierungsprozess machen, also musst du da mit den defines arbeiten.



  • Ich will auch versuchen, dir zu helfen. 😉

    Mati schrieb:

    Wie sieht es nun mit folgendem aus: Ich möchte 2 Vektoren abhängig vom user-input entweder mit double-werten oder mit complexen (2 doubles pro element) füllen. dies z.B. über ein einfaches inputargument.

    Hier ist die Information doch erst zur Laufzeit bekannt, oder? (Falls nicht, was meinst du mit user-input?)

    Dann würden dir Templates nicht viel helfen. Könntest du nicht eine gemeinsame Basisklasse für die beiden Typen einrichten und in der die Aktionen, die beide haben sollen, vereinheitlichen?

    class Base
    {
    public:
        virtual ~Base();
    
        // hier die gemeinsamen Methoden, z.B.
        virtual void print() = 0;
    };
    
    class Double : public Base // Wrapper um double
    {
    public:
        Double(double value) : value(value)
    
        virtual void print()
        {
            std::cout << "Double:  " << value << "\n";
        }
    
    private:
        double value;
    };
    
    class Complex : public Base // Wrapper um std::complex
    {
    public:
        Complex(const std::complex<double>& value) : value(value) {}
    
        virtual void print()
        {
            std::cout << "Complex:  " << value.real() << " + " << value.imag() << "i\n;
        }
    
    private:
        std::complex<double> value;
    };
    

    Dann könntest du in einem if entscheiden, welcher Typ nun erstellt werden soll:

    Base* zahl;
    if (doubleEingegeben())
        zahl = new Double(...);
    else
        zahl = new Complex(...);
    
    // speichere in Container oder so
    
    delete zahl;
    


  • hmmm....das versteh ich net....bei dem beispiel von dir drakon wollte ich es so machen aber da geht so nicht:

    class sequential
    {
      void do_this_stuff (int p /*...*/ ) 
       {
         // mach was..
       }
    };
    
    class parallel
    {
      void do_this_stuff (int p /*...*/ ) {
          //Das ist der Library call
          MPI_Comm_Rank();
      }
    };
    
    template<typename technique>
    class manager
    {
      void general_stuff ()
      {
       // mach allgemeines
      }
    
      void special ()
      {
       technique t; 
       t.do_this_stuff ( 2 ); 
      }
    };
    
    int main
    {
    #ifdef DParallel
      manager<parallel> m; 
    #else
      manager<sequential> m;
    #endif
    
      m.general_stuff ();
      m.special ();
    }
    

    Ja. Du willst ja, dass der Compiler den Code gar nicht erst sieht und das kannst du ja nur vor dem Kompilierungsprozess machen, also musst du da mit den defines arbeiten.

    Jetzt bin ich verwirrt - ich dachte gerade die #ifdefs auszulagern wäre die idee gewesen - mir ist schon klar dass das so nicht geht aber irgendwie war mein Ziel ein anderes...nur ein einziges mal ein #ifdef sozusagen....

    @der hüter der zeit: danke für deinen Vorschlag - ja ich verstehe so ungefähr was du meinst und denke so würde es gehen. Im endeffekt ist es halt aber wieder fast doppelter code um den man aber wohl nicht drumrum kommt 🙂 danke schonmal!



  • Wenn für alle Instanzen immer das gleiche Verhalten benötigt wird, würde ich gar nicht erst Templates nehmen.

    class manager
    {
    public:
        void general_stuff()
        {
        }
    
        void special
        {
        #ifdef DParallel
            parallel_special(); // die Aktionen bei parallel
        #else
            sequential_special(); // die Aktionen bei sequentiell
        #endif
        }
    };
    

    Tut mir leid, wenn wir etwas aneinander vorbeireden... Evtl. versteh ich dein Problem auch falsch 😞



  • hmm....ja ich bin am überlegen ob es denn noch alternativen gibt bezüglich des compilezeit polymorphismumses... danke für den weiteren vorschlag

    du meintest dann wohl eher sowas oder?

    class manager
    {
    public:
        void general_stuff()
        {
        }
    
        void special
        {
        #ifdef DParallel
            Parallel parallel;
            parallel.special1(); 
            parallel.special2();
            //...
        #else
            Sequential sequential;
            sequential.special1();
            sequential.special2();
        #endif
        }
    };
    

    Obwohl - so ists auch ein schmarrrn.....herrgott....



  • Du kannst ja die anderen möglichen Implementierungen ausschalten:

    // sollte immer gehen, also keine Auswahl
    class sequential
    {
      void do_this_stuff (int p /*...*/ )
       {
         // mach was..
       }
    };
    
    // wird nur wenn definiert angeboten
    #ifdef DParallel
    class parallel
    {
      void do_this_stuff (int p /*...*/ ) {
          //Das ist der Library call
          MPI_Comm_Rank();
      }
    }; 
    #endif
    

    Somit hast du ja das define ein einziges mal, nämlich dort, wo die Klasse definiert ist. Die anderen Implementierungen sind aber weiterhin möglich.



  • Wenn die Abläufe für beide Techniken (parallel und sequentiell) genau gleich aussehen, kannst du ja ein Funktionstemplate schreiben, um nicht Code duplizieren zu müssen:

    template <class Technique>
    void special_action()
    {
        Technique t;
        t.special1();
        t.special2();
    }
    
    void manager::special()
    {
    #ifdef DParallel
        special_action<Parallel>();
    #else
        special_action<Sequential>();
    #endif
    }
    

    Wenn du in special_action() auch auf private Member zugreifen musst, kannst du sie zu einer Memberfunktion machen.



  • @Der Hüter der Zeit:
    Sein aktuelles Problem ist, denke ich, dass er Code hat, der auf anderen Plattformen gar nicht gehen kann, weil es die Bibliothek dazu gar nicht gibt. Also hat er Funktionen, die es gar nicht gibt und dann reicht es nicht nur die template Instanzierung einzugrenzen.

    EDIT:
    Nein. Doch das geht, wenn die Funktion ein template ist.



  • Also wir sollten nochmal klar stellen, was du überhaupt willst.
    Grundsätzlich hast du ja 2 Probleme. Du willst eine gewisse Art von Polymorphie, also Teile des Code einfach austauschen ohne wirklich etwas ändern zu müssen. Meiner Meinung nacht ist statische Polymorphie (templates) immer noch die besser Alternative, weil du genau das willst.

    Das zweite Problem ist, dass gewisse Teile des Codes auf gewissen Platformen gar nicht funktionieren können, weil die Bibliotheken gar nicht vorhanden sind oder die Funktionen anderst heissen.

    Die Verwirrung, die hier entstanden ist, ist imo darauf zurückzu schliessen, dass die beiden Probleme gemischt wurden. Darum nochmal differenziert betrachtet. Du willst das hier:

    int main ()
    {
      //manager<parallel> m;
      manager<sequential> m;
    
      m.general_stuff ();
      m.special ();
    }
    

    Also sozuagen entweder ist ist der manager parallel oder sequentiell. Dafür eignet sich ja, wie schon gesehen das arbeiten mit einer Policy.
    Jetzt kommt das zweite Problem. Es können nicht alle parallel arbeiten, also soll, wenn die erste Zeile aktiv ist ein Compilerfehler kommen, wenn es nicht unterstützt wird, oder?

    Also können wir das parallel einfach mit einem ifdef umschliessen:

    struct sequential
    {
      void do_this_stuff ()
      {}
    };
    
    #ifdef DParallel 
    struct parallel
    {	
     void do_this_stuff () 
     {
       MPI_Comm_Rank();
     }
    };
    #endif
    

    Alternativ können wir da eine template Funktion benutzen, weil diese erst Kompiliert wird, wenn sie instanziert wird.

    struct sequential
    {
      template <class T>
      void do_this_stuff ()
      {}
    };
    
    struct parallel
    {
      template <class T>
      void do_this_stuff () 
      {
        MPI_Comm_Rank();
      }	
    };
    

    Das führt aber dazu, dass wir hier eine template Funktion haben, wo es eigentlich gar nicht nötig ist. (Sprich beim Funktionsaufuf muss ein template Parameter angegeben werden).

    Mir sind noch andere Möglichkeiten eingefallen, aber die zu diskutieren bringt es erst, wenn die Anforderungen klarer sind.
    Nun kannst du ja auch die Auswahl in der main mit einem define lösen, wenn du möchtest.



  • Danke euch beiden. Ich habe im Moment die template-variante mit dem umschließenden #ifdef. Somit also 2 #ifdefs - einmal in der main zum instantiieren und einmal das umschließende #ifdef des parallelen anteiles. so kompiliert es jetzt. ich arbeite weiter und melde mich bei meiner nächsten frage 😉 danke euch !



  • Ok - also die template-variante und auch die idee mit einer abstrakten basisklasse für die beiden typen double und complex geht jetzt. Mein letztes Problem ist nun beides unter einen Hut zu bringen.

    Dabei besteht mein Hauptproblem darin dass ich innerhalb der subklassen (double, complex) ebenfalls in den implementierten methoden auf die methoden aus sequential oder parallel zugreifen muss/will. also auch dort wieder auf ein objekt von manager zugreifen will sozusagen.

    Ich wieß aber nicht wie ich das bewerkstelligen soll. ich kann ja nicht ein objekt manager an Base (von double und complex) übergeben weil damit müsste ja base templatebasiert sein. andererseits macht es wenig sinn innerhalb von manager die sachen anzulegen oder wäre das der richtige weg?

    Oder anders gefragt:
    Wie kann denn eine Methode einer Klasse (hier Complex z.B) die Subklasse von Base ist auf eine templatebasierte Methode einer anderen Klasse zugreifen?


Anmelden zum Antworten