Anzeige:
Seite 1 von 2 12 LetzteLetzte
Ergebnis 1 bis 15 von 25

Thema: Probleme mit abgeleiteten Klassen

  1. #1
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177

    Probleme mit abgeleiteten Klassen

    Hi,

    ich habe ein Problem bei dem ich nicht weiter komme. Ich möchte Abgeleitete Klassen für einander austauschen.
    Sprich ich habe zwei Basisklassen Document und Mask. Von denen habe ich die Klassen BookDocument, BookMask,
    RecipeDocument, RecipeMask abgeleitet.
    In meinem Programm möchte ich nach belieben die Abgeleiteten Klassen für einander austauschen.

    Code:
    class Document{};
    
    class BookDocument: public Document{};
    
    class RecipeDocument: public Document{};
    
    class Mask{};
    
    class BookMask: public Mask{};
    
    class RecipeMask: public Mask{};
    
    if(test == "recipe"){
         document = static_cast<RecipeDocument *>(document);
         document = new RecipeDocument;
    		
         mask = static_cast<RecipeMask *>(mask);
         mask = new RecipeMask(document); 
    }
    else if(test == "book"){
         document = static_cast<BookDocument *>(document);
         document = new BookDocument;
    		
         mask = static_cast<BookMask *>(mask);
         mask = new BookMask(document); 
    }
    Zum einen bekomme ich Probleme bei der Vorwärts Deklaration,

    Code:
    class BookDocument: public Document{
    ...
    };
    
    class Document;
    Zum Anderen hat der static_cast nicht das gebracht was ich wollte.
    Code:
         document = static_cast<RecipeDocument *>(document);
    Und ich muss in jeder Methode der Abgeleiteten Klasse die Methode der Basis Klasse aufrufen:

    Code:
    void BookDocument::init(){
    
         Document::init();
    
    }
    Was auch nicht gerade schön ist.

    Gern kann ich auch den Quellcode hinauf laden. Doch wollte ich es erst einmal übersichtlich belassen.
    Geändert von dml (20-12-2013 um 12:41 Uhr)

  2. #2
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Ich bin jetzt wieder zu der braven Version zurück gekehrt:
    Code:
    class::class(){
         rdocument  = new RecipeDocument;
         rmask = new RecipeMask(document); 
    
         bdocument = new BookDocument;
         bmask = new BookMask(bdocument); 
    
    }
    
    void class::method(){
          if(test == "recipe")
                arbeite mit rdocument und rmask;
          else if(test == "book")
                arbeite mit bdocument und bmask;
    }
    Macht zwar deutlich mehr Aufwand, als ein Pointer auf das benötigte Element. Aber dafür Typsicherheit...

  3. #3
    Administrator Avatar von anda_skoa
    Registriert seit
    17.11.2001
    Ort
    Graz, Österreich
    Beiträge
    5.477
    Ich bin mir nicht ganz sicher, welchen Effekt du zu erreichen versucht hast.

    Die Casts im ersten Beitrag machen eigentlich keinen Sinn, auf beiden Seiten befindet sich die selbe Variable.

    Nehmen wir an "document" ist vom Type "Document" dann ändert der Cast die Sichtbarkeit der Typinformation auf, z.B., BookDokument
    Aber das wird nicht benutzt, denn das Resultat des Casts wird wieder nur auf "document" zugewiesen, was die sichtbare Typinformation wieder auf "Document" zurück bringt.

    Danach wird überhaupt gleich eine neue Instanz angelegt, welchen Typ die alte Instanz hatte ist damit ohnehin nicht länger relevant.

    Die Methode der Basisklasse musst du nur aufrufen, wenn du (a) die Methode in erster Linie ünerhaupt überschrieben hast und (b) du weiterhin die Funktionalität der Basisimplementierung ausgeführt haben möchtest.

    Das von dir angeführte Beispiel, also dass die überschriebene Methode ohnehin nichts anderes macht als die Basisimplementierng, wäre gleich bedeutend damit, die Methode erst gar nicht zu überschreiben.

    Wie gesagt lässt sich aus den vorhandenen Informationen leider nicht erkennen, welchen Effekt du gerne gehabt hättest.

    Ciao,
    _
    Qt/KDE Entwickler
    Debian Benutzer

  4. #4
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Hi anda_skoa,

    erst einmal vielen Dank für deine Hilfe. Mit dem cast hattest du Recht, er bringt wirklich nicht den Effekt den ich mir gewünscht hatte.
    Mein Problem ist, das ich einen Pointer deklarieren muss. der für zwei verschiedene Klassen im Programm funktionieren soll.
    Code:
    Document *document;
    im Verlauf des Programmes wird mehrmals eine Methode aufgerufen die den Pointer immer wieder neu füllen soll. Einmal mit RecipeDocument und ein anderes Mal mit Bookdocument.
    Code:
    void class::method(){
          if(test == "recipe")
                document = new Recipedocument;
          else if(test == "book")
                document = new BookDocument;
    }
    
    void class::clear(){
           delete document;
    }
    Ich werde morgen früh, sagen wir besser morgen Vormittag , Den Quellcode mal hinaufladen. Ich könnte zwar auch jetzt schon auf das Programm verweisen, doch es würde nicht wirklich etwas bringen, da die Problematik noch nicht darin enthalten ist.

  5. #5
    Administrator Avatar von anda_skoa
    Registriert seit
    17.11.2001
    Ort
    Graz, Österreich
    Beiträge
    5.477
    Das sollte so schon mal funktionieren, also die Basisklasse als Variablentype, getrenntes Erzeugen und einheitliches Löschen.

    Ciao,
    _
    Qt/KDE Entwickler
    Debian Benutzer

  6. #6
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Tja, was soll ich sagen. Ich habe es ausprobiert und es funktioniert.

    Wünsche Frohe Weihnachten!

  7. #7
    Administrator Avatar von anda_skoa
    Registriert seit
    17.11.2001
    Ort
    Graz, Österreich
    Beiträge
    5.477
    Das Wichtigste für das Löschen mittels Basisklassenpointer ist, dass der Destruktor der Basisklasse virtual ist.

    Ciao,
    _
    Qt/KDE Entwickler
    Debian Benutzer

  8. #8
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Hi,
    frohes Neues! Hat leider nicht geklappt. Der Kompiler hatte es akzetiert da beide von der gleichen Klasse sind. Methoden der Kindklasse wurden aber nicht akzeptiert.
    Ich habe jetzt einmal den Quellcode hinauf geladen:
    Quellcode

    Ich hoffe es macht die Sache nicht zu unübersichtlich.

    Versionen:
    0.4.0 (funktionierende Version mit nur einer Maske)
    0.4.1 (mehrere Masken mit Eltern->Kind Beziehung)
    aktuell: 0.4.1.2 (mehrere Masken zur besseren Übersicht ohne Eltern->Kind Beziehung)

    wichtige Stellen:
    MainWidget.h: 72 RecipeDocument *document;
    MainWidget.cpp: 231 document = new AnimalDocument;
    MainWidget.cpp: 238 document = new BookDocument;
    MainWidget.cpp: 245 document = new RecipeDocument;
    MainWidget.h: 54 enum BookType { EMPTY, ANIMAL, BOOK, RECIPE};
    Geändert von dml (24-01-2014 um 11:06 Uhr) Grund: Link entfernt, da niemand ihn mehr braucht.

  9. #9
    Administrator Avatar von anda_skoa
    Registriert seit
    17.11.2001
    Ort
    Graz, Österreich
    Beiträge
    5.477
    Ich konnte leider nicht so leicht erkennen, inwiefern sich die verschiedenen Dokumente unterscheiden.

    Die start() Methode scheint immer das gleiche zu machen, ebenso readDocument().

    In start() scheint dabei jeweils eine vom Dokumenttyp zugehörige Mask Instanz verwendet zu werden, die dann in ihren jeweiligen fill() Methoden unterschiedliche Sachen machen.

    Für diesen Fall sehe das dann in etwa so aus
    Code:
    class Mask
    {
    public:
        virtual ~Mask() {}
    
        virtual void fill( QString file_content, QFileInfo fileinfo ) = 0;
    };
    Code:
    class BookMask : public Mask
    {
    public:
        // Implementierung von BookMask::fill()
        void fill( QString file_content, QFileInfo fileinfo );
    };
    
    class RecipeMask : public Mask
    {
    public:
        // Implementierung von RecipeMask::fill()
        void fill( QString file_content, QFileInfo fileinfo );
    };
    Code:
    class Document
    {
    public:
        virtual ~Document() { delete mask; }
    
        // die gemeinsame Implementierung
        void start();
    
    protected:
        explicit Document(Mask *mask) : mask(mask) {}
    
        Mask *mask;    
    };
    Code:
    class BookDocument : public Document
    {
    public:
        BookDocument() : Document(new BookMask) {}
    };
    
    class RecipeDocument : public Document
    {
    public:
        RecipeDocument() : Document(new RecipeMask) {}
    };
    Also die Implementierung von start() ist für alle Document Klassen identisch, den Unterschied macht die jeweilige Mask Klasse bzw dessen fill() Methode.

    Das hier ist eine Art Basisfall, d.h. es gibt keine Gemeinsamkeiten von BookMask::fill() und RecipeMask::fill(), die Methode Mask::fill() ist daher "pure virtual" (virtual vorne und "= 0" am Ende).

    Wenn der aufrufende Code das hier ist:
    Code:
    Document *document = new BookDocument();
    document->start(....);
    dann wird in mask->fill() aufgerufen. Nachdem "this" in diesem Fall ein BookDocument ist, ist "mask" ein BookMask, d.h. BookMask::fill() wird aufgerufen.

    In deinem Code gibt es allerdings Gemeinsamkeiten, d.h. es gibt potentiell Möglichkeiten duplizierten Code zu vermeiden.
    Dafür gibt es mehrere Ansätze, die oft auch kombiniert werden können.

    Beispiel: in den beiden fill() Methoden, die ich mir angesehen habe, sind "check version" und "get color" identisch.
    Man könnte die also als Methoden in Mask (Basisklasse) hinzufügen
    Code:
    class Mask
    {
    protected:
       QString version;
       QColor color_cmb;
    
       void getVersion(QString file_content);
       void getColor(QString file_content);
    };
    Die fill() Methoden von BookMask und RecipeMask könnten dann so etwas machen
    Code:
    void BookMask::fill(QString file_content, QFileInfo fileinfo)
    {
        getVersion();
    
        // get name
    
        getColor();
    
        // get book specific data
    }
    Hier ist "get name" innerhalb des ansich gleichen Teils. Wenn die Reihenfolge egal ist, kann man dass natürlich in den "specific" Teil ziehen und sowas machen

    Code:
    class Mask
    {
    public:
        virtual void fill( QString file_content, QFileInfo fileinfo )
        {
            getVersion();
            getColor();
    
            // usw
        }
    };
    
    class BookMask : public Mask
    {
    public:
        virtual void fill( QString file_content, QFileInfo fileinfo )
        {
            // get common data
            Mask::fill(file_content, fileinfo);
    
            // get book specific data
        }
    };
    Wieder unter Annahme des aufrufenden Codes von oben: Document::start() ruft mask->fill() auf. "mask" ist vom Typ BookMask, also wird BookMask::fill() ausgeführt. Das leitet erstmal auf Mask::fill() weiter und macht dann seinen Rest.

    Wenn das nicht reicht, also wenn z.B. die Reihenfolge der Leseoperationen wichtig ist, z.B. "get name" zwar typspezifisch ist aber immer zwischen "get version" und "get color" gemacht werden muss, dann kann man das so lösen

    Code:
    class Mask
    {
    public:
        virtual void fill( QString file_content, QFileInfo fileinfo )
        {
            getVersion();
            getName();
            getColor();
    
            // usw
        }
    
    protected:
        virtual void getName() = 0;
    };
    
    class BookMask : public Mask
    {
    public:
        virtual void fill( QString file_content, QFileInfo fileinfo )
        {
            // get common data
            Mask::fill(file_content, fileinfo);
    
            // get book specific data
    
    protected:
        void getName()
        {
            // get name in book specific way
        }
    };
    In diesem Fall wird in Mask::fill() der Aufruf von getName() wieder zurück nach BookMask::getName() geleitet und danach mit dem gemeinsamen Code ("get version") weiter gemacht.

    Ciao,
    _

    P.S.: ein paar generelle Anmerkungen:

    * QFile kann am Stack, also ohne new, erzeugt werden wenn es nur innerhalb einer Methode benutzte wird. Das stellt sicher, dass das Objekt auch wieder zerstört wird, Derzeit hast du einige Leaks, z.b. buf_file in RecipeDocument::readDocument(), wenn das Öffnen fehlschlägt.

    * Wenn es über mehrere Methoden hinweg genutzt wird, sollte es zumindest im Destructor der Klasse gelöscht werden.

    * qDebug() << Q_FUNC_INFO << ....;
    Qt/KDE Entwickler
    Debian Benutzer

  10. #10
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Hi anda_skoa,

    erst einmal vielen Dank, das du dir die Mühe gemacht hast meinen Quellcode zu berichtigen!
    Mit den Leaks hast du Recht, da hat QFile mir immer in Verbindung mit QTextStram Probleme bereitet.
    Ich bin bei Version 0.4.1.2 von dem Ableiten von Klassen wieder weggegangen, da ich Glaubte für einen Drittten, der nur schnell Mal seine eigene Maske erstellen will, ist es so leichter. Gerne gehe ich wieder auf 0.4.1 zurück, ich kenne ja meinen Code.
    Danke für die vielen Tips!

  11. #11
    Administrator Avatar von anda_skoa
    Registriert seit
    17.11.2001
    Ort
    Graz, Österreich
    Beiträge
    5.477
    Zitat Zitat von dml Beitrag anzeigen
    Ich bin bei Version 0.4.1.2 von dem Ableiten von Klassen wieder weggegangen, da ich Glaubte für einen Drittten, der nur schnell Mal seine eigene Maske erstellen will, ist es so leichter.
    Beide Ansätze haben ihre Vor- und Nachteile.

    EIne komplexe Ableitungshierachie kann schwer zu verstehen sein, oder sogar falsch verstanden werden. In deinem Fall ist das aber sehr überschaubar.

    Auf der anderen Seite kann die Notwendigkeit, gemeinsamen Code zu kopieren auch schlecht sein. Zum Beispiel wird dann vielleicht Code kopiert und eingesetzt, der zwar scheinbar funnktioniert aber ungeahnte Nebeneffekte hat.

    Oder wenn Fehler in solchen Codesegmenten gefunden werden, müssen sie in allen Kopien ebenfalls gefunden und behoben werden.

    Eine dritte Möglichkeit ist, gemeinsamen Code unabhängig von Vererbung auszulagern und dann Aufrufe zu delegieren.

    Nehmen wir als Beispiel BookMask::fill(). Dort hast du im Wesentlichen zwei Arten von Funktionalität:
    * extrahieren aus dem Dateiinhalt
    * Füllen von Widgets

    In einer der Varianten habe ich einzelne Kombinationen davon in protected Methoden der Basisklasse ausgelagert, z.B. getColor().

    Diese Methode würde dabei sowohl das Extrahieren der Farbe, als auch das Setzen am ColorListEditor machen.

    Wir könnten das aber auch anders trennen, also z.B. so
    Code:
    class CommonDataParser
    {
    public:
        explicit CommonDataParser(QString file_content) : file_content(file_content) {}
    
        QString getVersion() const;
        QColor getColor() const;
    
    private:
        const QString file_content;
    };
    
    QString CommonDataParser::getVersion() const
    {
        static QRegExp exp(".*<version>(.*)</version>.*");
      		if (exp.exactMatch(file_content)){
    			     return exp.cap(1); 
    		  }
        return QString();
    }
    
    QColor CommonDataParser::getColor() const
    {
        static QRegExp exp(".*body. color: (#......); background-color: #......;.*");
    
        QColor color;
      		if (exp.exactMatch(file_content))
    			     color.setNamedColor(exp.cap(1));
    
        if (!color.isValid())
            color = Qt::black;
    
        return color;
    }
    BookMask::fill() kann dann einfach ein Objekt davon erzeugen und benutzen

    Code:
    void BookMask::fill(QString file_content, QFileInfo fileinfo)
    {
        CommonContentParser parser(file_content);
    
        version = parser.getVersion();
        color_cmb->setColor(parser.getColor());
    }
    Das lässt sich natürlich auch mit Ableitungen von Mask oder Document kombinieren.

    Einer der Vorteile dieser Auslagerung ist, dass man die ausgelagerte Funktionalität leichter testen kann, z.B. in automatischen Unittests mittels QTestLib.

    Ciao,
    _
    Qt/KDE Entwickler
    Debian Benutzer

  12. #12
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Stimmt, die getSaveString() Methode aufzuteilen, daran hatte ich gestern auch noch gedacht. Doch vorher wollte ich über den QXMLParser nachdenken. Denn den Bereich XML habe ich noch ausgelassen und Trickse das Html-Dokument eigentlich nur ein bisschen aus. Das ist vorher noch von mir nachzuprüfen.
    Beim Quellcode bin ich auch wieder zu der Vererbung zurückgekehrt. Das Projekt basiert auf OOP und wer eine eigene Maske erstellen will, muss das auch können und nicht wie du schon sagtest halb.
    Geändert von dml (03-01-2014 um 13:26 Uhr)

  13. #13
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Es gibt leider noch ein kleines Problem mit der Typumwandlung. Das Ableiten der Klassen hat wirklich sehr geholfen, doch weiß ich noch nicht wie ich das Objekt umwandeln kann.

    Code:
    mainwidget.cpp 318;		case RECIPE:
    mainwidget.cpp 319;		      qDebug()<<"\nRecipeDocument";
    mainwidget.cpp 320;// 		      mask     = rmask;
    mainwidget.cpp 321;// 		      document = rdocument;
    mainwidget.cpp 322;// 		      mask = dynamic_cast<RecipeMask*>(mask);
    mainwidget.cpp 323;// 		      document dynamic_cast<RecipeDocument*>(document);
    mainwidget.cpp 324;		      mask     = (RecipeMask*) mask;
    mainwidget.cpp 325;		      document = (RecipeDocument*) document;
    mainwidget.cpp 326;		      
    mainwidget.cpp 327;		      break;
    Es soll weiterhin das selbe Objekt sein und nicht auf ein Anderes zeigen.

    Edit:
    Nun gut, ich wollte etwas zusammenführen was nicht zusammen gehört. Bei einer theoretischen Umwandlung, müsste dennoch z.B. initialisiert werden. Ich rede mich jetzt mal einfach mit mangelnder Erfahrung herraus.
    Geändert von dml (24-01-2014 um 14:20 Uhr)

  14. #14
    Administrator Avatar von anda_skoa
    Registriert seit
    17.11.2001
    Ort
    Graz, Österreich
    Beiträge
    5.477
    Ich bin mir nicht ganz sicher, was du erreichen möchtest. Der Codeausschnitt gibt dazu zu wenig Informationen.

    Ich nehme an, du hast eine Variable mit dem Basistyp, also z.B. Document *document.
    Der zeigt in Falle von RECIPE auf eine Instanz von RecipeDocument.

    An einer bestimmten Stelle möchtest du mit diesem abgeleiteten Typ arbeiten, z.B. mit Funktionen die in RecipeDocument deklariert sind und in Document nicht vorhanden sind.

    Code:
    class Document
    {
    public:
        virtual ~Document() {}
    };
    
    class RecipeDocument : public Document
    {
    public:
        void printCalories(); // nur in RecipeDocument, nicht in Document
    };
    Code:
    Document *document = new RecipeDocument;
    
    // arbeiten mit den Methoden von Document
    
    // Fallunterscheidung für Spezialanforderungen
    
    switch (type) {
        case RECIPE:
            static_cast<RecipeDocument*>(document)->printCalories();
            break;
    }
    
    // oder für mehrere Aufrufe
    
    switch (type) {
        case RECIPE: {
            RecipeDocument *recipe = static_cast<RecipeDocument*>(document);
            recipe->printCalories();
            // andere RecipeDocument spezifische Aufrufe
            break;
        }
    }
    Oder wenn z.B. der Typ nicht bekannt ist
    Code:
    RecipeDocument *recipeDocument = dynamic_cast<RecipeDocument*>(document);
    if (receipeDocument != 0) {
        recipeDocument->printCalories();
    }
    Als generelle Empfehlung würde ich auf den Einsatz von Casts im C-Stil verzichten, also sowas
    Code:
    (RecipeDocument*)document
    Ciao,
    _
    Qt/KDE Entwickler
    Debian Benutzer

  15. #15
    Registrierter Benutzer
    Registriert seit
    02.08.2008
    Beiträge
    177
    Mein Problem war, das ich ein Objekt nach Bedarf umwandeln wollte:
    Code:
     mask     = (RecipeMask) mask; 
    ...
     mask     = (BookMask) mask;
    Doch das ist schon logisch nicht möglich. Da bei einer Typumwandlung der neue Typ eh neu aufgebaut werden muss. Also einen Konstruktor durchläuft.
    Hinzu kam, das das Signal erst nach Abarbeitung des Slots beendet wird und ich genau in dem Slot das Objekt löschen und neu initialisieren wollte.
    Da fehlt mir halt noch die Übung, aber danke das Du mir den cast noch einmal genauer erklärt hast.
    Auch sind die Mehtodenbezeichnungen MainWidget:newDocument() falsch ind irreführend. Mir fällt derzeit keine richtige Bezeichnung ein, sie müssten in etwa MainWidget::newDocumentAndMask() heissen.
    Geändert von dml (26-01-2014 um 15:36 Uhr)

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •