ifdefs über virtuelle subklassen auslagern?



  • 🙂 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?



  • 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.

    Warum hat man nicht 2 verschiedene Kompilierungseinheiten, die man entsprechend seinen Wuenschen zum gegeben Projekt dazu linkt. Gesteuert wird es halt ueber ein entsprechendes build script / system. Dann hat man weder stoerende ifdef's noch irgendwelche Templates. Der Code ist sauberer.



  • Warum hat man nicht 2 verschiedene Kompilierungseinheiten, die man entsprechend seinen Wuenschen zum gegeben Projekt dazu linkt. Gesteuert wird es halt ueber ein entsprechendes build script / system. Dann hat man weder stoerende ifdef's noch irgendwelche Templates. Der Code ist sauberer.

    wie meinst dudas? hast du ein kleines minimalbeispiel?



  • Headerfile "do_something.h" der Funktion:

    int do_something();
    

    Implementationsdatei "do_something_parallel.cpp":

    int do_something()
    {
      //do it parallel
    }
    

    Implementationsdatei "do_something_sequential.cpp":

    int do_something()
    {
      //do it sequential
    }
    
    #include "do_something_parrallel.h"
    
    int main()
    {
      do_something();
    }
    

    So, nu wird abhaengig davon, was man will, entweder die parallele Variante kompiliert oder die sequentielle. Ein Beispiel ohne makefile mit gcc fuer die sequentielle Variante:

    g++ do_something_sequential.cpp main.cpp
    


  • #include "do_something_parrallel.h"
    
    int main()
    {
      do_something();
    }
    

    fehlt da nicht was? so inkludierst du ja nur die parallele version. Oder muss man dann immer die main methode anpassen bevor kompiliert wird?


Log in to reply