Gültigkeitsbereiche oder: Dynamische Verwaltung von Objekten zur Laufzeit



  • Hallo zusammen,

    nun muss ich das Thema leider nochmal stressen.
    Aus vergangenen Posts hier habe ich gelernt, dass Objekte, welche durch Kopie in einen Container gelegt werden, so lange bestehen, wie der Container selbst.

    Nun habe ich folgende Situation:
    In einer Methode lege ich mir eine lokale Variable eines Vektors an, der Objekte (keine Zeiger) von einer meiner Klassen aufnehmen kann.
    Danach durchlaufe ich eine Schleife und prüfe, ob sich eines meiner AnimationsObjekte geändert hat. Wenn ja, "lege" ich sein umgebendes Rechteck (ein Objekt) in den besagten Vektor. Dieses Hineinlegen passiert innerhalb eines if-Bereiches. Der Vektor selbst hat in der gesamten Methode seinen Gültigkeitsbereich.

    Nun weist mein Logging aber darauf hin, dass, sobald der if-Bereich verlassen wird, Destruktoren der Rechtecke gerufen werden, die in den Vektor gelegt werden.
    Das erkenne ich daran, dass ich keinen entsprechenden Konstruktor-Aufruf dieser Rechteckobjekte finde - das kann nur am Kopierkonstruktor liegen. Diesen habe ich nicht selbst überschrieben, dort erfolgt kein Logging (nur im normalen Konstruktor).
    Das irritiert mich nun: Welches Objekt wird denn hier zertört? Der Vektor ist weiterhin gültig, daher sollten doch auch die dort abgelegten Objektkopien noch gültig sein, oder nicht?

    Was noch mehr irritiert: Die Methode, die mir einen Bezug zu diesen umgebenden Rechtecken zurück gibt, habe ich so implementiert, dass sie eine konstante Referenz auf diese Objekte zurück gibt! Diese Methodenaufrufe habe ich als Argumente der push_back()-Aufrufe meiner vector-Variable!!! 😮

    Laut dem, was ich gelesen habe über die STL-Container dürfen aber gar keine Referenzen darin abgelegt werden (schon gar keine konstanten)!!! Wie geht das denn?
    Der Compiler gibt keine Warnung und keinen Fehler aus! Zum Einsatz kommt ein GCC 4.2.4 PPC (AmigaOS) - an den bin ich gebunden.
    Verstehe die Welt nicht mehr!

    Das eigentliche Problem ist aber nicht das hier beschriebene, sondern, dass alles einfriert, wenn am Ende das Programm terminiert und alles aufräumt. Mein aktueller Verdacht ist, dass dies mit diesem ominösen Zerstören der Rechteckobjekte zusammenhängt (Zugriff auf bereits freigegebenen Speicher).

    Code kann ich gern noch nachreichen - ist halt dann nur ein Auszug. Wollte erst einmal alles tippen!

    Versteh hier gerade gar nix mehr!

    Dank euch schon mal für eure Ideen!

    Ciao



  • Reth schrieb:

    Code kann ich gern noch nachreichen - ist halt dann nur ein Auszug. Wollte erst einmal alles tippen!

    Du hast Dir zwar offensichtlich sehr viel Mühe gegeben alles genau zu beschreiben ( 👍 ), aber Code sagt trotzdem mehr als tausend Worte.



  • 😃 Habs schon befürchtet.

    Also dann:

    Hier die Deklaration meiner Methoden zur Rückgabe der Rechtecke aus der Header-Datei:

    const RectangleC& getCurrentBounds() const;
    const RectangleC& getOldBounds() const;
    

    Und hier der verwendende Code, der zur Laufzeit ermittelt, welche Rechtecke in Frage kommen und diese für die spätere Verarbeitung sammelt, mit ein paar eingefügten Kommentaren für diesen Thread hier:

    void AnimObjectManagerC::refreshAllObjects(WindowC& window)
    { // die umgebende Methode
    
        std::vector<RectangleC> rectangles; // <= Der Verdächtige
    ...
        // Zu ndernde Objekte und deren umgebende Rechtecke ermitteln
        for (std::vector<AnimObjectC *>::iterator innerIterator = animObjects.begin(); innerIterator != animObjects.end(); innerIterator++)
        {
            AnimObjectC *animObject = (*innerIterator);
            if (animObject->willAnimate())
            {
                rectangles.push_back(animObject->getOldBounds());      // wieso geht das ohne Warnung und Error? Hier wird ne Referenz zurück gegeben!!!
                rectangles.push_back(animObject->getCurrentBounds());  // wieso geht das ohne Warnung und Error? Hier wird ne Referenz zurück gegeben!!!
                ...
                // !!! UND HIER, NOCH VOR ENDE DES IF-BEREICHES WIRD EIN DESTRUCTOR-AUFRUF DER RECTANGLEC-KLASSE GELOGGT!!! OHNE DASS EIN KONSTRUKTOR-AUFRUF GELOGGT WURDE => DAHER DURCH KOPIERKONSTRUKTOR ANGELEGT
                // KANN NICHT BEWEISEN, DASS ES SICH UM DIE EBEN MIT push_back() EINGEFÜGTEN OBJEKTE HANDELT, DAFÜR MÜSSTE ICH WISSEN, WIE ICH DEN KOPIERKONSTRUKTOR SO BAUEN KANN, DASS ER "NUR" LOGGT UND ANSONSTEN DAS MACHT, WAS ER IM DEFAULT FALL MACHT - ICH HABE DEN KOPIERKONSTRUKTOR NICHT SELBST IMPLEMENTIERT!!!
            }
    

    Keine Ahnung, was da abgeht! Bin nur auf der Suche nach dem Fehler, der mir das ganze System beim Beenden einfriert. Im Verlaufe des Logs wird die Adresse, die hier als im Destruktor-Logging auftaucht immer wieder verwendet und ich habe auch mehrere Destruktor-Aufrufe dafür im Log. Immer wieder in dieser Schleife.

    Bin da noch völlig im Unklaren!



  • Reth schrieb:

    Hier die Deklaration meiner Methoden zur Rückgabe der Rechtecke aus der Header-Datei:

    const RectangleC& getCurrentBounds() const;
    const RectangleC& getOldBounds() const;
    

    Wäre spannend zu wissen, wie das denn implementiert ist. Nur um sicherzugehen: die Referenz geht aber auf ein Rechteck, das dem AnimObjekt gehört und zeigt nicht auf eine temporäre Variable? Ein Rechteck hört sich nach 4 Zahlen an - die man vermutlich auch einfach kopieren könnte.

    rectangles.push_back(animObject->getOldBounds());      // wieso geht das ohne Warnung und Error? Hier wird ne Referenz zurück gegeben!!!
    

    Warum soll das nicht gehen? Das push_back kopiert einfach das Rechtangle in den vector. Und wenn der vector größer wird als seine aktuelle Capacity, werden die Objekte sogar innerhalb des vectors nochmal umherkopiert.

    Um nochmal zu dem Punkt "warum soll das nicht gehen" zurückzukommen: nimm an, du hast einen vector<int> vi; und versuchst nun vi.push_back(1); - dann ist 1 auch eine Variable vom Typ int und trotzdem funktioniert das push_back.

    Da du ja schon selbst herausgefunden hast, dass ein vector<referenz> nicht geht, übrigens weil Referenzen wie ein Alias sind und nicht neu zugewiesen werden können. Deswegen muss du hier einen anderen Weg gehen, wenn du deine Rectangles nicht kopieren willst. Am einfachsten wäre wohl ein vector<Rectangle*> .

    Und noch ein allgemeiner Rat: wenn du über alle Elemente einer Collection loopen willst, arbeite am besten mit der foreach-Schleife. Also dies hier

    for (std::vector<AnimObjectC *>::iterator innerIterator = animObjects.begin(); innerIterator != animObjects.end(); innerIterator++)
        {
            AnimObjectC *animObject = (*innerIterator);
    

    ersetzt du durch:

    for (AnimObjectC *animObject : animObjects)
        {
    

    oder besser noch, wenn du deine Objekte nicht änderst, füge noch ein "const" hinzu.



  • wob schrieb:

    Wäre spannend zu wissen, wie das denn implementiert ist. Nur um sicherzugehen: die Referenz geht aber auf ein Rechteck, das dem AnimObjekt gehört und zeigt nicht auf eine temporäre Variable?

    Ja, selbstverständlich!

    wob schrieb:

    rectangles.push_back(animObject->getOldBounds());      // wieso geht das ohne Warnung und Error? Hier wird ne Referenz zurück gegeben!!!
    

    Warum soll das nicht gehen? Das push_back kopiert einfach das Rechtangle in den vector. Und wenn der vector größer wird als seine aktuelle Capacity, werden die Objekte sogar innerhalb des vectors nochmal umherkopiert.

    Na weil die Methoden keine Rechteckobjekte, sondern konstante Referenzen darauf zurück geben. Daher hätte ich erwartet, dass der Compiler meckert, weil man Referenzen nicht in Container einfügen darf.

    wob schrieb:

    Um nochmal zu dem Punkt "warum soll das nicht gehen" zurückzukommen: nimm an, du hast einen vector<int> vi; und versuchst nun vi.push_back(1); - dann ist 1 auch eine Variable vom Typ int und trotzdem funktioniert das push_back.

    Das Bsp. findet man überall, aber ein Bsp. mit komplexen Objekten o.ä. ist schwer bzw. kaum zu finden!

    wob schrieb:

    Da du ja schon selbst herausgefunden hast, dass ein vector<referenz> nicht geht, übrigens weil Referenzen wie ein Alias sind und nicht neu zugewiesen werden können. Deswegen muss du hier einen anderen Weg gehen, wenn du deine Rectangles nicht kopieren willst. Am einfachsten wäre wohl ein vector<Rectangle*> .

    Ja, das habe ich auch schon gedacht, wollte aber nochmal nachfragen.

    Aber hat noch jemand ne Idee, welche Rechtecke denn bei der von mir oben bezeichneten Stelle zerstört werden und out-of-scope gehen? Da der aufnehmende vector noch unverändert ist würde ich nicht erwarten, dass es die Kopien der übergebenen Rechtecke sind! Was aber dann?
    Oder wie kann ich sonst raus bekommen, ob die Rechtecke im vector noch OK bzw. gültig sind, oder ob ich auf bereits freigegebenen Speicher zugreife????

    wob schrieb:

    Und noch ein allgemeiner Rat: wenn du über alle Elemente einer Collection loopen willst, arbeite am besten mit der foreach-Schleife. Also dies hier

    for (std::vector<AnimObjectC *>::iterator innerIterator = animObjects.begin(); innerIterator != animObjects.end(); innerIterator++)
        {
            AnimObjectC *animObject = (*innerIterator);
    

    ersetzt du durch:

    for (AnimObjectC *animObject : animObjects)
        {
    

    oder besser noch, wenn du deine Objekte nicht änderst, füge noch ein "const" hinzu.

    Danke! Muss ich mir mal anschauen.

    Generell muss ich nicht an der hier vorgestellten Weise festhalten. Was ist denn ein guter bzw. euer Weg, zur Laufzeit solche Dinge zu tun? Aus einer Sammlung einer Menge, diejenigen zu finden und zu merken, die man danach bearbeiten will (und die anderen links liegen zu lassen)?
    Überall Listener o.ä. registrieren? Oder einen ganz anderen Aufbau nehmen?
    Dachte mir, dass die STL-Container hier praktisch sind. Aber durch die ganze Kopiererei, die Nicht-Nutzbarkeit von Referenzen und das eigentliche wieder rückbesinnen auf Zeiger finde ich die Container mehr und mehr unpraktisch!
    Oder wo hab ich da noch nen Denkfehler? (wie gesagt: Ich muss am GCC 4.2.4 für PPC festhalten!)



  • Schau Dir mal die Ausgabe von

    #include <vector>
    #include <iostream>
    
    struct Foo
    {
    	int i;
    
    	Foo(int i) : i{ i } { std::cout << this << " Foo::Foo(" << i << ") i = " << i << '\n'; }
    	Foo(Foo const & other) : i { other.i } { std::cout << this << " Foo::Foo(Foo const &) i = " << i << '\n'; }
    	Foo(Foo && other) noexcept : i{ other.i } { std::cout << &other << " --> " << this << " Foo::Foo(Foo &&) i = " << i << '\n'; }
    	~Foo() { std::cout << this << " Foo::~Foo() i = " << i << '\n'; }
    };
    
    int main()
    {
    	std::vector<Foo> v;
    	// v.reserve(9);
    
    	for (int i = 1; i <= 9; ++i) {
    		std::cout << i << ":\n";
    		{
    			Foo f{ i };
    			std::cout << "push_back(): ";
    			v.push_back(f);
    		}
    		std::cout << v.size() << " of " << v.capacity() << "\n\n";
    	}
    }
    

    mit und ohne v.reserve(9); an. Vielleicht geht Dir dann der Knoten auf.



  • Danke! Muss ich mal austesten (hoffentlich kann das mein alter Compiler!).

    Bin leider nach wie vor nicht sehr firm mit C++, v.a. nicht mit neueren Konstrukten usw. (hab in den letzten Jahren kaum was in dieser Richtung gemacht).

    && ist ja der Move-Operator (mit dem kenne ich mich überhaupt nicht aus) - fürchte, da steigt mein Compiler aus (wie gesagt GCC 4.2.4, der hat den C++11 Standard nicht an Board).

    Spielst Du evtl. darauf an, dass der vector durch sein dynamisches Rezising dafür sorgt, dass hier Destruktoren gerufen werden?

    Dazu nochmal meine andere Frage: Was nutzt ihr zur Handhabung eines solchen Problems, wie ich es beschrieben habe (also aus einer Menge an Objekten zu Laufzeit, die auszusuchen und zu merken, mit denen gearbeitet werden muss, um diese danach in einer Reihe/Schleife abzuarbeiten)?



  • Reth schrieb:

    Dazu nochmal meine andere Frage: Was nutzt ihr zur Handhabung eines solchen Problems, wie ich es beschrieben habe (also aus einer Menge an Objekten zu Laufzeit, die auszusuchen und zu merken, mit denen gearbeitet werden muss, um diese danach in einer Reihe/Schleife abzuarbeiten)?

    1. ohne C++11 macht C++ keinen Spaß 😞

    2. Schleifen kurz halten!

    for (TheIteratorType it = collection.begin(); it != collection.end(); ++it) {
      if (selectElement(*it)) do_something(*it);
    }
    

    Oder gibt es einen Grund, warum du erst alle selecten und danach alle abarbeiten willst?



  • Reth schrieb:

    && ist ja der Move-Operator (mit dem kenne ich mich überhaupt nicht aus) - fürchte, da steigt mein Compiler aus (wie gesagt GCC 4.2.4, der hat den C++11 Standard nicht an Board).

    Dann entsorge den move-ctor.

    Reth schrieb:

    Spielst Du evtl. darauf an, dass der vector durch sein dynamisches Rezising dafür sorgt, dass hier Destruktoren gerufen werden?

    Ja. Du kannst live zusehen, wie std::vector fröhlich Copy-Konstruiert und verschiebt, wobei natürlich Destruktoren aufgerufen werden.



  • @Swordfish:
    Danke Dir!

    @wob:

    wob schrieb:

    Oder gibt es einen Grund, warum du erst alle selecten und danach alle abarbeiten willst?

    Ja. In der Folge passieren Clipping- und weitere Grafikaufrufe der darunter liegenden API. Diese sollen nur für notwendige Teile und am Stück gemacht werden.

    Auch werden die Clipping-Bereiche in dieser Methode nur gesammelt, woanders kommen sie dann zur Anwendung (im drunter liegenden API). Daher ist es egal, ob das Clipping direkt in der Schleife gemacht, oder dort nur die notwendigen Teile gesammelt werden. Das Resultat des Sammelns kommt nach der Schleife zum Einsatz und setzt den Clippingbereich in dem Fenster, in dem die Grafik aktualisiert werden soll.


Log in to reply