Dynamisches Generieren von Klassen mit Makro- oder Template-Programmierung



  • Hallo,

    es muss eine unbekannte Anzahl von (Kind-)Klassen dynamisch erstellt werden. Ich habe etwas mit Makros und Templates experimentiert, bin bisher aber auf keinen grünen Zweig gekommen.

    Hat jmd. evtl. eine Idee wie man an das Problem rangehen könnte?

    Beispiel:

    Die Eltern-Klasse ist bekannt, die Klasse(n) Kindx (Kind1, Kind2 usw.) müssen je nach Anzahl der Kinder dynamisch generiert werden. Die Kind-Klassen sehen immer identisch aus, nur der Klassenname ändert sich.

    class Eltern {
    
        public:
        void doIt();
    
    }
    
    class Kindx : public Eltern {
    
        public:
        void doKindx();
    
    }
    


  • class Eltern {
        public:
        void doIt();
    }
    
    template<int N>
    class Kind : public Eltern {
        public:
        void doKind();
    }
    
    int main(){
        Kind<1> k1;
        Kind<1294> k2;
    }
    

    Ich wüsste aber nicht, wozu man das will. Wenn die Kinder alle gleich sind, wozu dann verschiedene Klassen?

    Du kannst übrigens keine Klassen dynamisch erstellen. Alle Klassen müssen zu Compilezeit bekannt sein.



  • Wenn "dynamisch" zur Laufzeit heißt: nein.

    Kind ist von Eltern abgeleitet? Das sieht ziemlich sinnlos aus.
    Warum brauchst du für jedes Kind eine eigen Klasse, die sich inhaltlich nicht von den anderen unterscheidet?



  • Dynamisch VOR der Laufzeit (Präprozessor). Es geht darum, dass die Klassen automatisiert erstellt werden müssen.

    Stimmt, das Beispiel ist etwas unglücklich. Jede Kind-Klasse hat eine eigene Ausprägung der doKind()-Methode



  • nicnoc schrieb:

    Jede Kind-Klasse hat eine eigene Ausprägung der doKind()-Methode

    Und wie unterscheiden sich die? Wo ist der automatisierbare Teil?

    Wäre gut, wenn du uns mitteilen könntest, was du eigentlich erreichen willst.


  • Mod

    Das geht recht einfach, mit Boost.PP:

    #include <boost/preprocessor/repetition/repeat.hpp>
    
    struct Eltern
    {
        void doIt();
    };
    
    #define DEFINE_KIND(z, i, data) \
    struct Kind##i : public Eltern  \
    { \
        void doKind(); \
    };
    
    BOOST_PP_REPEAT(50, DEFINE_KIND,)
    
    int main(){
        Kind1 k1;
        Kind49 k2;
    }
    

    🤡



  • Und wie unterscheiden sich die? Wo ist der automatisierbare Teil?

    Die Kind-Klassen unterscheiden sich ausschließlich in der doKind()-Methode, die Implementierung dieser Methode muss von Kind zu Kind unterschiedlich ausfallen können.

    Der Ansatz von nwp3 ist ganz hilfreich gewesen, ich müsste jetzt nur zusätzlich noch in der Lage sein, die doIt()-Methode der Eltern-Klasse von einer Kind-Instanz aus aufrufen zu können.

    @Arcoth
    Danke für den Hinweis aber ich will aufgrund der HW-nahmen Programmierung/Portabilität ohne zusätzliche Libs o.ö. auskommen.



  • nicnoc schrieb:

    Die Kind-Klassen unterscheiden sich ausschließlich in der doKind()-Methode, die Implementierung dieser Methode muss von Kind zu Kind unterschiedlich ausfallen können.

    Was genau willst du automatisieren, wenn die doIt() -Methode sowieso unterschiedlich ist? Wie willst du überhaupt eine "unbekannte Anzahl" dieser abgeleiteten Klassen erstellen?

    Bitte erklär dein Problem vernünftig, sonst kommen wir hier auf keinen grünen Zweig...

    nicnoc schrieb:

    ich müsste jetzt nur zusätzlich noch in der Lage sein, die doIt()-Methode der Eltern-Klasse von einer Kind-Instanz aus aufrufen zu können.

    Und wieso sollte das ein Problem darstellen?



  • class Eltern{
    public:
    	void doIt();
    };
    
    template<int N>
    class Kind : public Eltern{
    public:
    	void doKind();
    };
    
    void Kind<1>::doKind(){
    	cout << "Ich tu was";
    }
    void Kind<42>::doKind(){
    	cout << "Ich tu was ganz anderes";
    }
    
    int main(){
    	Kind<1> k1;
    	Kind<1294> k2;
    }
    


  • @nwp3: Danke, das Prinzip ist mir jetzt klar!



  • Wobei ich immer noch nicht sehe, was hier das Template bringt. Das Gleiche kann man mit normaler Vererbung erreichen.

    Oder ist das Problem ernsthaft die 2 Zeilen Code für die Klassendefinition?



  • Ich denke mit klassischer Vererbung würde man vermutlich prinzipiell genauso weit kommen. In meinem Fall ist die Anzahl der Kind-Instanzen jedoch "unbekannt" (variabel wäre wohl passender) im Sinne von mal sind es nur 2, dann 4, dann 10 usw.

    D.h. ich muss in der Lage sein, die Kind-Klassen quasi "dynamisch" VOR der Laufzeit zur generieren. Würde man statt den Template-Ansatz die klassische Vererbung nehmen müsste jedes Mal der Code angepasst werden. .

    Zumindest ist soweit mein Verständnis der ganzen Sache ...

    Klar, man muss jede Kind-Klassen Methode doKind() individuell implementieren aber das ist für mich ok.



  • Das macht immer noch keinen Sinn. Ein Template bringt dir hier keine zusätzliche Variabilität. Du musst genauso jede Methode einzeln implementieren, wie wenn du direkt erben würdest. Bei der Vererbung hast du zudem die Möglichkeit, sinnvolle Klassennamen statt nur Instantiierungen mit Zahlen zu wählen.

    Mit anderen Worten: Du hast rein gar nichts automatisiert.



  • Dann erkläre mir bitte wie Du mit einer Variablen Anzahl von benötigten Kind-Klassen umgehen würdest.

    class Eltern {
    
    	public:
    	Eltern() {}
    	void doEltern() {}
    };
    
    class Kind1 : public Eltern {
    
    	public:
    	Kind1() : Eltern() {}
    	void doKind() {}
    };
    
    class Kind2 : public Eltern {
    
    	public:
    	Kind2() : Eltern() {}
    	void doKind() {}
    };
    
    ...
    
    class KindN : public Eltern {
    
    	public:
    	KindN() : Eltern() {}
    	void doKind() {}
    };
    

    D.h. ich muss in der Lage sein, die Kind-Klassen quasi "dynamisch" VOR der Laufzeit zur generieren. Würde man statt den Template-Ansatz die klassische Vererbung nehmen müsste jedes Mal der Code angepasst werden. .

    Ein solches Vorgehen ist für mich nicht praktikabel, daher finde ich den aus Deiner Sicht nicht automatisierten Ansatz besser. Das die doKind()-Methoden individuell angepasst werden müssen stellt für mich kein Problem dar und ist im Moment noch hinnehmbar. Natürlich wäre es schicker den kompletten Code generieren zu lassen, aber das ist im Moment einfach unnötig.



  • nicnoc schrieb:

    Dann erkläre mir bitte wie Du mit einer Variablen Anzahl von benötigten Kind-Klassen umgehen würdest.

    Die ist nicht variabel, sondern zur Compilezeit bekannt.
    Da kann man sie genauso gut von Hand schreiben.
    Reden wir mal anders:
    Was ist die Eltern-Klasse genau und was sollen die einzelnen Kind Klassen machen?*
    Vielleicht würden wir das ja ganz anders lösen.

    *Edit: Yeah, konsistente Bentzung von Bindestrichen.



  • Beschreib mal wodrum es wirklich geht. (Nicht dass das ein XY-Problem ist)

    Denn, wenn du N Kindklassen brauchst (mit N verschiedenen Implementierungen), du dir aber "nur" die N Klassendefinitionen per Makro/Template schreibst, hast du ungefähr 5 Minuten Tipparbeit durch x Stunden Gesuche und Probiere ersetzt, was dir letztlich mehr als nichts gebracht hat.

    Was ich mir vorstellen könnte wäre, dass du M Kindklassen implementierst, aber dir ein Makro oder ein Template so baust, dass N Kindklassen genutzt werden (mit N < M). Aber auch da ist die Frage wofür?!

    Wofür braucht man eine unbestimmte Anzahl an Klassen vor dem Kompilieren (Templates und Metaprogramming mal ausgenommen ;)), die dann auch noch verschiedene Implementierungen haben?



  • Es geht hier generell um Systemtasks. Die Eltern-Klasse entspricht der Task-Klasse und die Kinder der jeweiligen Task-Spezialisierung. Genauer gesagt entspricht die Kind-Methode doKind() dem jeweiligen Doing der entsprechenden Taskspezialisierung.

    Pesudocode:

    void Eltern::doEltern() {
    
        createTask(func,this);
    }
    
    void Eltern::func(void* param) {
    
        static_cast<Eltern *>(param)->doKind();
    }
    
    void main() {
    
        /* Kind aka. Task wird angelegt */
        Kindx kindx(name, alter);
        /* doEltern erstellt einen Systemtask und ruft über die C-Funktion
        createTask() die Methode func() mit "this" als Übergabeparameter auf und
        startet abhängig von der Kind-Klasse bzw. Task-Klasse die doKind()-Methode*/
        kindx.doEltern();
    }
    

    Da ich jetzt pseudo automatisiert die spezialisierten Task-Klassen erzeugen kann ist der Ansatz von nwp3 für mich ausreichend.

    Allerdings habe ich jetzt bei dem static_cast() das Problem, dass jetzt offensichtlich keine genaue Zuordnung zwischen Kind-Klasse und der Methode doKind() vorhanden ist und eine beliebige doKind()-Methode aufgerufen wird.

    Nachtrag: Das Problem mit dem static_cast habe ich nicht wenn ich die klassische Vererbung verwende.



  • Sorry Guys, ich hab da nurn dickes Fragezeichen nach mehrmaligen Lesen übern Kopf oO 😕



  • Was spricht gegen:

    class task
    {
    private:
      std::function<void(void)> fnc_; // oder function-pointer/template wenn kein c++11
    
      static void helper(void *data)
      {
      	 static_cast<task*>(data)->fnc_();
      }
    
    public:
    	task(std::function<void(void)> fnc)
    	: fnc_(std::move(fnc)) {}
    
    	void do_task()
    	{
    		create_task(helper, this);
    	}
    };
    
    int main()
    {
    	task a(function_pointer), b(functor()), c([](){std::cout << "Hallo Welt!"});
    	a.do_task();
    	b.do_task();
    	c.do_task();
    }
    

    (Hab create_task einfach so übernommen ohne zu wissen, was es genau tut.)


  • Mod

    @Nathan: helper sollte wohl static sein.


Log in to reply