Basisklasse übergeben?



  • puh...also danke schonmal...

    Einzige Lösung:
    Dein "main.cpp" in verschieden Header (.h) und Implementiereungen (.cpp) aufteilen. Dann reicht im Header eine Forward-Deklaration, im .cpp wird dann der jeweils andere Header mit includiert.
    Im Prinzip sollte es reichen, Pattern von den restlichen Klassen zu separieren.

    aj aj aj...ich überlege wie ich das ändern kann weil mir der lösungsvorschlag zu aufwändig/seltsam ist. Evtl. ist bei mir falsches design die grundlage...

    hmm...



  • //Base.h
    #ifndef Bs_H
    #define Bs_H
    
    //#include "Pattern.h" // nein!
    class Patern; // ja!
    
    // die main
    class Base
    {
            public:
                    virtual void To_Pattern(Pattern*& P) = 0;      
    
    };
    
    #endif
    

    An eine Funktion das this übergeben macht aber keine Probleme! Das hatte ich so nicht erwartet.
    Aber funktionieren tut es nur, wenn das Objekt mit "new" erstellt wurde. Sonst -> SegFault!

    Nein, nein, nein. Du verstehst das etwas völlig falsch!

    void modify(Test* t) // hier wird der this Zeiger kopiert
    {
        cout << "modifiy " << t << endl;
        delete t; // das ist ganz klar böse, wenn du ein automatisches Objekt hast
        t = new Test; // das weist t einen neuen Zeiger zu und nicht dem Objekt, auf welches t vorher gezeit hat.
    }
    

    Ich hoffe du verstehst das jetzt, ansonsten muss ich wirklich auch noch mit ASCII Art anfangen. :p



  • Hier einfach mal die Lösung zu deinem Problem.
    Es geht nicht einfacher bei solchen gegenseitigen Abhängigkeiten.

    Base.h

    #ifndef BASE_H
    #define BASE_H
    
    class Pattern;
    
    class Base 
    { 
        public: 
            virtual void To_Pattern(Pattern* P) = 0;
    };
    
    class D : public Base 
    { 
        public: 
            void To_Pattern(Pattern * P);
    };
    
    #endif
    

    Base.cpp

    #include <iostream>
    
    #include "Base.h"
    #include "Pattern.h"
    
    void D::To_Pattern(Pattern * P)
    {
        std::cout << "D1:Base" << std::endl; 
        //auf P arbeiten 
    }
    

    Pattern.h

    #ifndef PATTERN_H
    #define PATTERN_H
    
    class Base;
    
    class Pattern 
    { 
        public:
            void foo(Base* base);
    }; 
    
    #endif
    

    Pattern.cpp

    #include "Pattern.h"
    #include "Base.h"
    
    void Pattern::foo(Base* base)
    { 
        base->To_Pattern(this); 
    }
    

    main.cpp

    #include "Base.h"
    #include "Pattern.h"
    
    int main(int argc, char** argv) 
    {
        Base * base = 0; 
        D d1;
        base = &d1; 
        Pattern pat; 
        pat.foo(base); 
        return 0; 
    }
    

    Kompilieren:

    $ g++ -c Pattern.cpp
    $ g++ -c Base.cpp
    $ g++ main.cpp -o main Base.o Pattern.o
    $ ./main
    
    void modify(Test* t) // hier wird der this Zeiger kopiert
    {
        cout << "modifiy " << t << endl;
        delete t; // das ist ganz klar böse, wenn du ein automatisches Objekt hast
        t = new Test; // das weist t einen neuen Zeiger zu und nicht dem Objekt, auf welches t vorher gezeit hat.
    }
    

    In der letzten Zeile dein Kommentar:
    Ich habe auch gedacht, dass t nun auf ein neues Objekt, was ja definitiv an einer anderen Stelle im Speicher liegen müsste, zeigt.
    Die ganzen Test-Ausgaben vor, während und nach dem Modify zeigen mir in dem Fall aber ein anderes Bild:

    0x603010
    modifiy 0x603010
    destroy 0x603010
    0
    *** 0x603010
    

    Es ist IMMER die selbe Adresse, demnach wird die selbe Speicherstelle überschrieben... Oder bin ich da jetzt wieder total daneben?

    Ein späteres

    t = new Test;
    cout << t << endl;
    t = new Test;
    cout << t << endl;
    

    liefert wie erwartet

    0x603030
    0x603050
    


  • Mach mal das hier:

    void modify(Test* t) // hier wird der this Zeiger kopiert
    {
        cout << "modifiy " << t << endl;
        delete t; 
        t = new Test; 
        cout << "modifyed t: " << t << endl;
        delete t; // jetzt auch wieder aufräumen
    }
    

    Du kannst t ja gar nicht kennen ausserhalb der Funktion.. Du hast klar immer den gleichen Wert ausgegeben.

    ...
            cout << this << endl; (1)
            modify(this); (2)
        }
        ~Test()
        {
            cout << "destroy " << this << endl; (3)
        }
    ...
    
    0x603010 (1)
    modifiy 0x603010 (2) (innerhalb von modify)
    destroy 0x603010 (3)
    0 ??
    *** 0x603010 ??
    

    Mit dem obigen Code müsste zwischen 2 und 3 ein anderer Zeiger ausgegeben werden.



  • Hmmm...

    #include <iostream>
    using namespace std;
    
    struct Test;
    void modify(Test*);
    
    struct Test
    {
        void modifyThis()
        {
            cout << "=== Test::modifiyThis() (this == " << this << ") === " << endl;
            modify(this);
            cout << "after modify() " << this << endl;
            cout << "======" << endl;
        }
        ~Test()
        {
            cout << "destroy " << this << endl;
        }
    };
    
    void modify(Test* t)
    {
        cout << "modifiy " << t << endl;
        delete t;
        t = 0;
        t = new Test;
        cout << "modified t: " << t << endl;
        delete t;
    }
    
    int main()
    {
        Test* t = new Test;
        t->modifyThis();
        cout << "*** " << t << endl;
        delete t;
    }
    

    Das finale "delete t" in modify() beschert mir einen segfault beim finalen delete in main()...
    Ebenso wie ein weiterer Aufruf von modifyThis().
    Ich find das lustig, weil ich ohne weiteres noch mit dem Objekt arbeiten konnte, auf Member zugreifen usw. aber ein zweites delete geht in die Hose.

    Außerdem, egal an welcher Stelle ich noch die Adresse ausgeben lasse (in Test::modifyThis(), modify() oder main()), immer ist es die Selbe...

    Du kannst t ja gar nicht kennen ausserhalb der Funktion.. Du hast klar immer den gleichen Wert ausgegeben.

    Hmm, ich kann doch belibig Zeiger kopieren und dann vergleichen, das sollte bei egal wie gearteten Aufrufen bei gleichen Adressen das gleiche Ergebnis liefern.
    Ob ich in einer Memberfunktion jetzt "this" mit einem übergebenen Pointer vergleiche oder zwei Pointer in einer freien Funktion oder direkt, oder ...

    Ich glaub ich muss da nochmal drüber schlafen...
    Vllt. küsst mich die Erkenntnis ja noch 😕

    Grüße und Danke



  • Ich habe doch gesagt, dass das obige delete böse per se ist.

    #include <iostream>
    using namespace std;
    
    struct Test;
    void modify(Test*);
    
    struct Test
    {
        void modifyThis()
        {
            cout << "=== Test::modifiyThis() (this == " << this << ") === " << endl;
            modify(this);
            cout << "after modify() " << this << endl;
            cout << "======" << endl;
        }
        ~Test()
        {
            cout << "destroy " << this << endl;
        }
    };
    
    void modify(Test* t)
    {
        cout << "modifiy " << t << endl; // ok, gibt das gleiche, wie this
        //delete t; // böse, böse böse! 
        t = 0; // ok
        t = new Test; // ok
        cout << "modified t: " << t << endl; //ok, sollte was anderes ausgeben
        delete t; // ok
    }
    
    int main()
    {
        Test* t = new Test; // ok
        t->modifyThis(); //ok
        cout << "*** " << t << endl; //ok, gibt das gleiche, wie this
        delete t; // wenn das innere delete aktiviert ist, wird hier der speicher erneut zerstört und das gibt den fehler!
    }
    


  • Lästermaul schrieb:

    Das finale "delete t" in modify() beschert mir einen segfault beim finalen delete in main()...

    Das ist nicht verwunderlich, der Speicher wurde ja bereits freigegeben.

    Lästermaul schrieb:

    Ich find das lustig, weil ich ohne weiteres noch mit dem Objekt arbeiten konnte, auf Member zugreifen usw. aber ein zweites delete geht in die Hose.

    Ziehe keine Schlüsse aufgrund solcher Aktionen. Sobald ein Objekt zerstört wurde, resultiert der Zugriff darauf in undefiniertem Verhalten. Das bedeutet, es ist reines Glück, falls das Programm abstürzt.

    Lästermaul schrieb:

    Hmm, ich kann doch belibig Zeiger kopieren und dann vergleichen, das sollte bei egal wie gearteten Aufrufen bei gleichen Adressen das gleiche Ergebnis liefern.
    Ob ich in einer Memberfunktion jetzt "this" mit einem übergebenen Pointer vergleiche oder zwei Pointer in einer freien Funktion oder direkt, oder ...

    Du übergibst eben eine Kopie des Zeigers, deshalb führt das t = new Test; in modify() nicht dazu, dass der originale Zeiger neu zugewiesen wird. Der this -Zeiger kann nämlich nicht verändert werden.



  • @Lästermaul

    Lies Dir mal _genau_ durch was "by Value" bedeutet. Wenn Du erinen Zeiger by Value übergibst (was Du bei deinem Methodenaufruf nunmal machst) dann wird für den Scope der Methode ein temporärer Zeiger angelegt. Alle Veränderungen des Zeigers innerhalb der methode (new, delete etc) finden NUR auf diesem temporären Zeiger statt und sind daher außerhalb der Methode nicht relevant.

    Natürlich können mehrere Zeiger auf das gleiche Objekt im Speicher zeigen, weist Du aber einem der Zeiger mittels new ein neues Objekt zu, hat das _keine_ Auswirkung auf die anderen Zeiger die nach wie vor auf das alte Objekt zeigen.

    Um die Änderung am Zeiger innerhalb der Methode auch außerhalb sichtbar zu machen mußt Du den Zeiger "by Reference" übergeben, als sowas in der Art:

    void modify(Test*& t)
    {
    }
    


  • Nexus schrieb:

    Lästermaul schrieb:

    Ich find das lustig, weil ich ohne weiteres noch mit dem Objekt arbeiten konnte, auf Member zugreifen usw. aber ein zweites delete geht in die Hose.

    Ziehe keine Schlüsse aufgrund solcher Aktionen. Sobald ein Objekt zerstört wurde, resultiert der Zugriff darauf in undefiniertem Verhalten. Das bedeutet, es ist reines Glück, falls das Programm abstürzt.

    Und genau das denk ich mir schon die ganze Zeit!!! Undefiniertes Verhalten.
    Dann ist der Kommentar von loks bezügl. delete auf eine Kopie eines Zeigers auch falsch. Ich bin erleichtert!

    Du übergibst eben eine Kopie des Zeigers, deshalb führt das t = new Test; in modify() nicht dazu, dass der originale Zeiger neu zugewiesen wird. Der this -Zeiger kann nämlich nicht verändert werden.

    Dass man this (den this-Zeiger) nicht ändern kann, hab ich doch auch gesagt, trotzdem wurde mir wiedersprochen 😞
    Das mit der Kopie des Zeigers ist mir jetzt auch klar.
    Werd mir das nochmal in Ruhe anschauen, ob ich das alles jetzt richtig begreife...



  • loks hat schon Recht.

    Du musst unterscheiden zwischen dem Zeiger als Objekt an sich und dem Speicherbereich, auf den er zeigt.

    int* a = new int(4);
    int* b = a;   // beide Zeiger zeigen auf 4
    delete b;     // gibt die angeforderte Vier frei
    b = 0;        // a ist nun NICHT Null.
    

    Wenn du einen Zeiger als Value-Parameter übergibst, d.h. void modify(Test* t) , ist das eine Kopie, wie hier im Beispiel b . Änderungen am Speicherbereich dahinter sind von ausserhalb der Funktion sichtbar, Änderungen am Zeiger selbst jedoch nicht. Und zu letzteren gehört nun mal t = new Test .



  • Nexus schrieb:

    loks hat schon Recht.
    [...]

    Wenn du einen Zeiger als Value-Parameter übergibst, d.h. void modify(Test* t) , ist das eine Kopie, wie hier im Beispiel b . Änderungen am Speicherbereich dahinter sind von ausserhalb der Funktion sichtbar, Änderungen am Zeiger selbst jedoch nicht. Und zu letzteren gehört nun mal t = new Test .

    loks schrieb:

    Alle Veränderungen des Zeigers innerhalb der methode (new, delete etc) finden NUR auf diesem temporären Zeiger statt und sind daher außerhalb der Methode nicht relevant.

    Die allgemeine Formulierung ist aber irreführend.
    Es mag sein dass das delete für den Zeiger (!) außerhalb von modify() irrelevant ist. Trotzdem ist es im Allgemeinen nicht irrelevant, denn das delete verändert vllt. nicht den Zeiger, aber den Speicherbereich - Das Objekt auf welches der Zeiger zeigt wird gelöscht. Ab hier sind Zugriffe auf das Objekt undefiniert.



  • Okay, da stimme ich dir zu, seine Aussage ist in der Tat etwas missverständlich.



  • Lästermaul schrieb:

    Nexus schrieb:

    loks hat schon Recht.
    [...]

    Wenn du einen Zeiger als Value-Parameter übergibst, d.h. void modify(Test* t) , ist das eine Kopie, wie hier im Beispiel b . Änderungen am Speicherbereich dahinter sind von ausserhalb der Funktion sichtbar, Änderungen am Zeiger selbst jedoch nicht. Und zu letzteren gehört nun mal t = new Test .

    loks schrieb:

    Alle Veränderungen des Zeigers innerhalb der methode (new, delete etc) finden NUR auf diesem temporären Zeiger statt und sind daher außerhalb der Methode nicht relevant.

    Die allgemeine Formulierung ist aber irreführend.
    Es mag sein dass das delete für den Zeiger (!) außerhalb von modify() irrelevant ist. Trotzdem ist es im Allgemeinen nicht irrelevant, denn das delete verändert vllt. nicht den Zeiger, aber den Speicherbereich - Das Objekt auf welches der Zeiger zeigt wird gelöscht. Ab hier sind Zugriffe auf das Objekt undefiniert.

    ja, stimmt, das delete erzeugt hier ein Problem je nah Reihenfolge. Wenn man erst new aufruft und dann wieder delete passiert ausserhalb gar nix. Ruft man aber erst ein delete auf wie in den Beispielen hier, dann erzeugt man Probleme.



  • Also ich komme mit der Lösung von Lästermaul immer noch nicht zurecht.
    Wenn ich jetzt in der KLasse D (die unter anderem templateklasse ist)
    innerhalb der methode To_Pattern das Pattern P verändern will schreit der compiler mit:

    Fehler: invalid use of undefined type »struct Pattern«
    Fehler: forward declaration of »struct Pattern«
    

    Hier nochmal mein angepasstes Minimalbeispiel:

    //Base.h

    #ifndef BASE_H
    #define BASE_H
    
    #include <iostream>
    
    class Pattern;
    
    class Base
    {
            public:
                    virtual void To_Pattern(Pattern* P) = 0;
    };
    
    template <class T> class D : public Base
    {
            public:
                    void To_Pattern(Pattern * P)
                    {
                      std::cout << "D1:Base" << std::endl;
                      P->var = 2;
                        //std::cout<< P->var << std::endl;
                        //auf P arbeiten
                    }
    };
    
    #endif
    

    //Pattern.h

    #ifndef PATTERN_H
    #define PATTERN_H
    
    class Base;
    
    class Pattern
    {
            public:
                    Pattern() { var = -1; }
                    void foo(Base* base);
    
                     int var;
    };
    
    #endif
    

    //Pattern.cpp

    #include "Pattern.h"
    #include "Base.h"
    
    void Pattern::foo(Base* base)
    {
        base->To_Pattern(this);
    }
    

    //Main.cpp

    #include "Base.h"
    #include "Pattern.h"
    
    #include <iostream>
    
    int main(int argc, char** argv)
    {
            Base * base = 0;
            D<int> d1;
            base = &d1;
            Pattern pat;
            pat.foo(base);
            std::cout<< pat.var << std::endl;
            return 0;
    }
    


  • Weil du natürlich nicht alles so übernommen hast, sondern in Base.h direkt auf Pattern zugreifen willst, obwohl das nur ne Forward-Declaration ist.
    Am besten die Implementierung von Base in eine Base.cpp auslagern, dann "schreit" der Compiler auch nicht mehr.
    Bei mir ist der übrigens total ruhig. Nur der CPU-Lüfter dreht etwas schneller (trotzdem nicht laut) wenn ich gaaaanz viel kompilieren tu.



  • Danke Lästermaul.
    Mir ist deine Lösung schon klar aber sie deckt sich nicht mit dem was ich erreichen will (sorry für den missstand).

    Ich WILL ja Pattern verändern können in der subklasse D. Nur fehlt mir dafür die Lösung...Ich kann doch die Implementierung von Base bzw. D nicht eine cpp-datei packen weil D template-basiert ist! Oder steh ich auf dem schlauch?



  • Gafabi schrieb:

    Danke Lästermaul.
    Mir ist deine Lösung schon klar aber sie deckt sich nicht mit dem was ich erreichen will (sorry für den missstand).

    Ich WILL ja Pattern verändern können in der subklasse D. Nur fehlt mir dafür die Lösung...Ich kann doch die Implementierung von Base bzw. D nicht eine cpp-datei packen weil D template-basiert ist! Oder steh ich auf dem schlauch?

    Nein du stehst nicht auf dem Schlauch, ich dachte das ist immer noch das alte Beispiel und habs nur überflogen und das template nicht gesehen. template gabs vorher ja noch nicht 😉

    Ersetze in Base.h die Vorab-Deklaration von Pattern durch ein #include, das funktioniert in dem jetzigen Fall.
    Hoffe da kommt nicht noch mehr Komplexität dazu 🙂



  • Ich passe jetzt die Lösung von Lästermaul an weil ich absolut nicht verstehe warum eine exception in der Klasse D::To_Pattern() dann nicht mehr geworfen wird bzw. von main empfangen wird. Irgendwie hängt die this-übergabe damit zusammen weil ich mit anderen methoden innerhalb Pattern problemlos exceptions werfen kann.

    [quote="Lästermaul"]Hier einfach mal die Lösung zu deinem Problem.
    Es geht nicht einfacher bei solchen gegenseitigen Abhängigkeiten.

    Base.h

    #ifndef BASE_H
    #define BASE_H
    
    class Pattern;
    
    class Base 
    { 
        public: 
            virtual void To_Pattern(Pattern* P) = 0;
    };
    
    class D : public Base 
    { 
        public: 
            void To_Pattern(Pattern * P);
    };
    
    #endif
    

    Base.cpp

    #include <iostream>
    
    #include "Base.h"
    #include "Pattern.h"
    
    void D::To_Pattern(Pattern * P)
    {
        std::cout << "D1:Base" << std::endl; 
        // es passiert was böses und ich möchte von hier heraus
        // eine exception nach main werfen:
        if (böse) throw std::runtime_error("Es ist was böses passiert.");
        //auf P arbeiten 
    }
    

    Pattern.h

    #ifndef PATTERN_H
    #define PATTERN_H
    
    class Base;
    
    class Pattern 
    { 
        public:
            void foo(Base* base);
    }; 
    
    #endif
    

    Pattern.cpp

    #include "Pattern.h"
    #include "Base.h"
    
    void Pattern::foo(Base* base)
    { 
        base->To_Pattern(this); 
    }
    

    main.cpp

    #include "Base.h"
    #include "Pattern.h"
    
    int main(int argc, char** argv) 
    {
        Base * base = 0; 
        D d1;
        base = &d1; 
        Pattern pat; 
        try {
           pat.foo(base); 
       } catch ( std::runtime_error& ex )
       {
         // hierher wird nie gesprungen obwohl die exception eintritt
        }
    
        return 0; 
    }
    


  • Ich bräuchte schon noch Hilfe. Mein Poblem ist seltsamerweise dass der code meines letzten Posts für eine serielle Implementierung funktioniert. Hier wird also die exception bis nach oben weitergereicht. Im Falle von paralleler umgebung, also wo mehrere prozesse eine Pattern Instanz haben, bzw. jeder Prozess (CPU) eine
    eigene hat, hier keine durchreichung nach oben geschieht. Woran kann das liegen?

    Danke



  • Gafabi schrieb:

    Im Falle von paralleler umgebung, also wo mehrere prozesse eine Pattern Instanz haben, bzw. jeder Prozess (CPU) eine
    eigene hat, hier keine durchreichung nach oben geschieht. Woran kann das liegen?

    Jetzt ist es eine Multi-Prozess-Anwendung?!?
    Und wie immer: wir brauchen Code... So ohne Anhaltspunkt kann man gar nichts sagen.

    Vor allem bin ich mir nicht sicher, ob das nicht ein Spur zu hoch wird 😃 (sry...)


Anmelden zum Antworten