Basisklasse als Templateparameter übergeben (sinnvoll???)



  • Hi,

    ich bin gerade auf folgendes gestoßen:

    "Exotische Kombinationen von Templates und Vererbung

    (...)

    Eine Basisklasse kann als Templateparameter übergeben werden:
    template <class T>
    class A: public T { ... };
    Sinnvolle Anwendung eines derart kreativen Umgangs mit den Möglichkeiten der Sprache sind nicht offensichtlich. Zur Überraschung selbst langjähriger Kenner von C++ hat sich jetzt herausgestellt, dass es sie gibt. Sowohl den Compilern als auch den Programmierern wird dabei aber einiges abgefordert.

    (...)"

    Nun möchte ich gerne wissen, ob davon wirklich so stark abzuraten ist und, falls jemand so etwas schon gemacht hat, worauf ich achten muss.
    (Was den Programmieren so "abgefordert" wird).

    Ich hatte nämlich überlegt, genau so etwas zu machen.
    Ich bin noch Neuling bei C++ und insofern kann es natürlich auch sein, dass ich eine für die folgende Situation viel bessere Möglichkeit übersehen habe:

    Ich habe dabei eine relativ umfassende Klasse, in dem Fall den Spielercharakter, der sich seine Eigenschaften so zusammenerben soll.

    Dabei wollte ich es so aufbauen, dass es die Klasse "Charakterklasse" mit einigen grundlegenden Eigenschaften und Funktionen gibt, welche an die einzelnen Klassen (Kämpfer, Magier, ...) vererbt werden.
    Diese ergänzen die Merkmale dann entsprechend (ein Magier halt mit Zauberkrams etc.).

    Der Spielercharakter soll dann, je nachdem, um welche Klasse es sich handelt, die Merkmale der entsprechenden Klasse erben.
    Zur Verdeutlichung.

    Charakterklasse (mit Dingen, die jeder hat - Lebensenergie z.B. idealerweise)
    ↓ . . . . . . . . . . . .↓ . . . . . . . . . . .↓
    Magier . . . . Schurke . . . . . Kämpfer (etc.)
    (alle mit den spezifischen Eigenschaften)

    ↓ , wobei eine von ihnen an den Charakter vererbt wird (neben einigen anderen Dingen).

    Charakter

    Kann/ Muss man das so machen, oder gibt es da eine sinnvollere Lösung?

    Schonmal vielen Dank im Voraus,

    Sinthoras

    P.S.: Seid mir nicht böse, wenn ich hier totalen Mist erzählt habe, ich sehe das Projekt eher als Übung für OOP.
    Bei dem Projekt handelt es sich übrigens nicht um ein Spiel, wie man wohl denken muss, sondern eine Art Hilfsprogramm.



  • Ich weiss nicht was an der Sache jetzt irgendwie speziell oder exotisch oder sonstwas sein sollte.

    Je nach Fall kann es Sinn machen eine Templateparameter als Basisklasse zu verwenden oder auch nicht. Auf jeden Fall ist es nicht grundsätzlich schlecht oder grundsätzlich gut.

    Bloss kann ich mit deinem Beispiel nix anfangen -- wie würde das aussehen wenn du das "Template leitet sich von Templateparameter ab" irgendwo anwendest - also mit deinen Magiern und Kriegern etc.? Könntest du das kurz skizzieren und dazuschreiben was du damit erreichen willst? - dann könnte ich ggf. auch sagen ob ich es für sinnvoll halte oder nicht 😉



  • Also es handelt sich um ein Programm, dass einem als Spielleiter bei einem Rollenspiel (erstmal D&D) das Auswürfeln und -rechnen von NSC (Nicht-Spieler-Charakteren) abnehmen soll.
    Keine Ahnung, ob Du damit was anfangen kannst, aber soweit zum Hintergrund des Ganzen.

    Ich habe es mit halt so gedacht, dass man zu Beginn einige grundlegende Angaben zum gewünschten Charakter macht (Volk, Alter, Geschlecht, Beruf, Machtstufe, evtl. auch sozialer Stand) und das Pogramm dann das Ermitteln der spieltechnischen Werte übernimmt.
    Man macht also zu Anfang diese Angaben, z.B. , dass man einen Zwergen Krieger der Stufe 3 oder so haben möchte.
    Das Programm würde dann in diesem Fall die Klasse "Krieger" an den Charakter vererben.
    ( etwa: Klasse<Krieger> Char1 o.ä.)
    Daneben erhält er dann noch Ausrüstung u.ä. und einige Veränderung aufgrund seines Volkes.

    Vielen Dank für Deine Hilfe.



  • Das hat meiner Meinung nach noch gar nichts so mit Templates zu tun, das über Templates zu regeln wäre ziemlicher overhead oder sehe ich das falsch?

    Ich denke mit normaler vererbung kommst du dort noch am einfachsten weiter.

    class npc
    {
    int hp;
    int mp
    int str;
    .
    .
    .
    virtual void berecheWerte() = 0;
    };
    
    class Krieger : public npc
    {
         void berechneWerte()
         {
            //..do some calculation
         }
    
    };
    
    class Magier : public npc
    {
        void berechneWerte()
        {
           //.. do some other calculation
        }
    };
    

    Ich sehe hier noch gar keinen so wirklichen sinn gleich Templates an zu setzen.Ggf kannst du das berechne Werte nochmal virtualisieren um das ganze für verschiedene Spielsystem nochmals zu überladen.



  • Kurz: Es kann sinnvoll sein 🙂 Bei dir würde ich das nicht als sinnvoll erachten, weil du kein festes Interface fedinierst und wenn neue Klassen hinzukommen, kann es sein, dass diese eine Methode nicht definieren. Das führt dann allerdings zu nicht gerade durchsichtigen Fehlermeldungen.

    Meine letzte Anwendung dafür waren Iteratoren, die sich nur darin unterscheiden, dass der eine konstant war und der andere nicht. Der Rest des Algorithmus war gleich. Daher habe ich das dann einfach folgendermaßen gehandhabt:

    template <typename T>
    struct unconst_btree_iterator
    {
        typedef T value_type;
        typedef T* pointer;
        typedef T& reference;
    
        typedef btree_node<T>* node_link_type;
        };
    
    template <typename T>
    struct const_btree_iterator
    {
        typedef T value_type;
        typedef const T* pointer;
        typedef const T& reference;
    
        typedef const btree_node<T>* node_link_type;
    };
    
    template
    <
        typename T,
        template <class> class constness = unconst_btree_iterator
    >
    struct btree_iterator
    {
        typedef typename constness<T>::value_type value_type;
        typedef typename constness<T>::pointer pointer;
        typedef typename constness<T>::reference reference;
    
        //Inhalt der Klasse...
    };
    
    template <typename T>
    class Binarytree
    
    public:
        typedef btree_iterator<T, unconst_btree_iterator> iterator;
        typedef btree_iterator<T, const_btree_iterator> const_iterator;
    
        //...
    };
    

    Damit muss ich die Algorithmen nur einmal schreiben 😉

    Edit: Mh, irgendwie war ich gerade geistig umnachtet. Das war natürlich ein völlig falsches Beispiel ....

    Besseres Beispiel aus der Loki-Bibliothek von Alexandrescu: die Threading-Policy.
    Dort wird von der Klasse, die eine Threadingvariante repräsentiert geebrt. Dabei wird eine eingebettete Klasse Lock übernommen:

    template <class Host>
    class SingleThreaded
    {
    public:
        struct Lock
        {
            Lock() {}
            Lock(const Host&) {}
        };
    };
    
    template <class Host>
    class ObjectLevelLockable
    {
    public:
        class Lock;
        friend class Lock;
    
        class Lock
        {
            //diese Klasse tut nun tatsächlich etwas bei der Konstruktion
        };
    };
    
    //usw.
    
    //Nutzung:
    
    template
    <
        template <class T> class Threading
    >
    class Foo : public Threading<class T>
    {
        doSome()
        {
            Lock lock;
            //etwas machen
        }
    };
    


  • Ok, danke.

    Dann werde ich es wohl ohne Templates machen.

    Kann mir trotzdem noch jemand sagen, warum der Linker bei ein und demselben Code manchmal arbeitet und manchmal abschmiert (undefined reference to...)?

    Edit:
    Hmmm... obwohl...
    Wenn ich, nehmen wir mal an, sechs verschiedene Klassen und sechs verschiedene Völker habe, dann muss ich von jeweils einem Vertreter beider Sparten die Merkmale zusammenfügen.
    Und da könnte ich das doch mit Templates machen, oder?

    Sonst würde ich ja nie fertig:

    class Charakterklasse {...};
    
    class Magier : public Charakterklasse {...};
    class Kaempfer : public Charakterklasse {...};
    class Schurke : public Charakterklasse {...};
    (...)
    
    class Volk {...};
    
    class Mensch : public Volk {...);
    class Zwerg : public Volk {...);
    class Ork : public Volk {...);
    (...)
    

    Und dann bräuchte ich für jede Kombination wieder eine eigene Klasse, oder nicht?
    Wobei Ausrüstung etc. ja noch hinzukommt.

    class MenschKrieger: public Mensch, public Kaempfer {...};
    class MenschMagier: public Mensch, public Magier {...};
    

    ...und so weiter...

    Mit Templates könnte man das doch so etwa lösen, oder?

    template<class Klasse, class Volk>
    class NSC : public Klasse, public Volk
    {... blabla alles hübsch kombinieren...};
    

    Oder sehe ich das jetzt falsch?

    Edit2: Ach ja, und dann wäre da noch dieser seltsame Linker-Fehler (s.o.) Kann den jemand erklären?



  • Ja, das ist völlig in Ordnung und auch gut. Nennt sich policy-basierters Klassendesign. 😉



  • Ok, danke.

    Und kannst du was mit dem Linker-Error anfangen?

    "undefined reference to" und dann folgt der Konstruktor meiner Klasse, also z.B.
    Klasse<Kaempfer>::Klasse()

    Aber halt nur manchmal.

    Deswegen steige ich da auch nicht durch...

    Edit:
    Ich füge nochmal den Code an.
    Aber an dem kann's ja eigentlich nicht liegen, wenn's mal geht und mal nicht...

    // klasse.h
    #include <iostream>
    #include <string>
    #include "char.h"
    #include "kaempfer.h"
    
    #ifndef _KLASSE_H_
    #define _KLASSE_H_
    
    template <class T>
    class Klasse : public T, public Char
    {
          public:
          Klasse();
    };
    #endif
    
    // klasse.cpp
    #include <iostream>
    #include "klasse.h"
    #include "char.h"
    
    template <class T>
    Klasse<T>::Klasse():Kaempfer()
                   {
                       name = "Sinthoras";
                       iTW = 1;
    };
    
    // main.cpp
    (...)
    Klasse<Kaempfer> NSC;
    (...)
    

    Manchmal klappt es halt...
    Eben auch als ich die Zeile "Klasse<Kaempfer> NSC;" auskommentiert habe (es scheint also wirklich am Konstruktor zu liegen) und seltsamerweise auch, als ich "klasse.cpp" (mit dem Konstruktor) als Test per #include in main eingebunden habe. Soll man ja nich machen.

    Edit2: Also irgendwie habe ich das Gefühl, er peilt nicht, dass die Konstruktor zu Klasse nicht in klasse.h sondern in klasse.cpp ist.
    Aber die Dateien sind hübsch alle in einem Projekt und manchmal gehts ja irgendwie...



  • Bei templates müssen Funktionsprototyp und Implementation in der selben Einheit sein.

    // klasse.h
    #include <iostream>
    #include <string>
    #include "char.h"
    #include "kaempfer.h"
    
    #ifndef _KLASSE_H_
    #define _KLASSE_H_
    //
    template <class T>
    class Klasse : public T, public Char
    {
          public:
          Klasse();
    };
    
    #include "klasse.cpp"  //hier so
    #endif
    
    // klasse.cpp
    #include <iostream>
    //#include "klasse.h" und das weg
    #include "char.h"
    
    template <class T>
    Klasse<T>::Klasse():Kaempfer()
                   {
                       name = "Sinthoras";
                       iTW = 1;
    };
    

    oder gleich alles in den header



  • Ok, danke, vielen Dank, euch allen.

    Edit:
    Hm, wenn ich es so einbinde, bekomme ich dann folgende Meldungen:

    expected constructor, destructor, or type conversion before '<' token
    expected `;' before '<' token

    beides bezogen auf die Zeile:

    Klasse<T>::Klasse():Kaempfer()

    Wenn ich es direkt im Header mache, kommt wieder "undefinde reference to..".



  • struct A
    {
    };
    
    struct Char
    {
        std::string name;
        int iTW;
    };
    
    template <class T>
    class Klasse : public T, public Char
    {
    public:
        Klasse();
    };
    
    template <class T>
    Klasse<T>::Klasse()
    {
        name = "Sinthoras";
        iTW = 1;
    };
    

    Schluckt mein Compiler :o



  • Z.Z. übersetzt er beide Versionen (also auch die falsche), wenn er lustig ist.

    Wenn gerade lieber abstürzt, macht er auch das... 😡

    😕



  • So mir war grad langweilig, hab dir hier nen Vorschlag. Schau mal ob er dir gefällt.

    // Getestet, Pseudofunktionen als Bildschirmausgabe als Kontrolle obs geht
    #include <iostream>
    
    using namespace std;
    
    // Charakter Typen
    class Magier{
    public:
        static void angreifen(){
            cout << "zaubere();" << endl;
        }
    };
    
    class Kaempfer{
    public:
        static void angreifen(){
            cout << "kaempfe();" << endl;
        }
    };
    
    class Schurke{
    public:
        static void angreifen(){
            cout << "tu_schurkiges();" << endl;  // naja Namen sind so ne Sache ;)
        }
    };
    
    // Volktypen
    class Mensch{
    public:
        static void rede( const std::string& text ){
            cout << "sprich_menschisch( text );" << endl;
        }
    };
    
    class Zwerg{
    public:
        static void rede( const std::string& text ){
            cout << "sprich_zwergisch( text );" << endl;
        }
    };
    
    class Ork{
    public:
        static void rede( const std::string& text ){
            cout << "sprich_orkisch( text );" << endl;
        }
    };
    
    // Spielfigur-Template
    template< typename CharakterT, typename VolkT >
    class Spieler{
    public:
        void handle(){
            VolkT::rede( "Halt, oder ich schiesse!" );
            CharakterT::angreifen();
        }
    };
    
    int main()
    {
    // Jede Kombination möglich
        Spieler< Schurke, Zwerg > spieler1;
        Spieler< Kaempfer, Ork > spieler2;
        Spieler< Kaempfer, Mensch > spieler3;
    // Alle gleich zu bedienen aber verschiedenes Verhalten
        spieler1.handle();
        spieler2.handle();
        spieler3.handle();
    }
    

    Du kannst nun jeden Spieler individuell erstellen, und auch jederzeit neue Charaktere oder Völker hinzufügen.
    Nennt sich, wie schon gesagt, policy-based design und ist sehr flexibel.



  • Ja, danke, so hatte ich mir das vorgestellt 🙂

    Sinthoras:

    template<class Klasse, class Volk> 
    class NSC : public Klasse, public Volk 
    {... blabla alles hübsch kombinieren...};
    

    Entspricht ja auch ungeähr dem, was mich damit meinte.

    Eine Frage aber noch:

    Wenn ich das hier

    template< typename CharakterT, typename VolkT >
    class Spieler{
    public:
        void handle(){
            VolkT::rede( "Halt, oder ich schiesse!" );
            CharakterT::angreifen();
        }
    };
    

    so (oder so änhlich) mache

    template< typename CharakterT, typename VolkT >
    class Spieler : public CharakterT, public VolkT {
    public:
        (...)
    };
    

    Kann ich die Funktionen doch direkt aufrufen oder?
    Also in main() z.B.
    Spieler<Krieger, Zwerg> Gimli;
    Gimli.angreifen();

    Oder nicht?

    Danke jedenfall für Deine Mühe.



  • Hmm brauchst nicht unbedingt erben.
    Kannst auch einfach weiterleiten:

    template< typename CharakterT, typename VolkT >
    class Spieler{
    public:
        void angreifen(){
            CharakterT::angreifen();
        }
    };
    
    //...
    Gimli.angreifen();
    //...
    

    Der Vorteil davon ist, dass du keine vtable für die virtuellen Funktionen brauchst. Die Weiterleitung wird garantiert jeder einigermassen schlaue Kompiler wegoptimieren.



  • Aber ganz ohne Vererbug werde ich wohl nicht auskommen, denn ich möchte ja, dass der Magier Zauber bekommt, unser Freund Gimli hier aber nicht.

    Wenn ich dann einfach in der Klasse Magier entsprechende Funktionen einbaue, in der Klasse Krieger aber nicht, hat sie am Ende eben auch nur der, der vom Magier erbt.

    Also so hatte ich mir das jedenfalls gedacht...



  • Sinthoras schrieb:

    Aber ganz ohne Vererbug werde ich wohl nicht auskommen, denn ich möchte ja, dass der Magier Zauber bekommt, unser Freund Gimli hier aber nicht.

    Wenn ich dann einfach in der Klasse Magier entsprechende Funktionen einbaue, in der Klasse Krieger aber nicht, hat sie am Ende eben auch nur der, der vom Magier erbt.

    Also so hatte ich mir das jedenfalls gedacht...

    Ok da ist was dran.


Anmelden zum Antworten