Objektzeiger von TListBox auf Gültigkeit prüfen?



  • Guten Abend in die Runde,

    in einer ListBox möchte ich gern den Namen von user-erzeugten Objekten (einer eigenen Klasse) anzeigen lassen und über ne Funktion auf bestimmte Eigenschaften des verknüpften Klassenobjekts zugreifen. So weit so gut.
    Über

    listbox->Items->AddObject(obj->getName(), reinterpret_cast<TObject*>(obj))
    

    kann ich mir das verknüpfen und andersrum auch wieder drauf zugreifen.

    Was ist jetzt aber wenn das Objekt gar nicht mehr existiert? Da ich die Liste bei Änderungen laufend aktualisieren lassen möchte, würd ich gern sowas schreiben:

    if ( !reinterpret_cast<myclass*>(listbox->Items->Objects[j]))
    {
     listbox->Items->Delete(j);
    }
    

    aber funktioniert natürlich nicht (im ungünstigen Fall sogar doch), weil die Speicheradresse zwar freigegeben ist, aber der Zeiger der ListBox ja davon unberührt bleibt. (und das könnte irgendwann dann böse enden..).

    Gibt es irgendne Möglichkeit zu prüfen, ob das Objekt worauf der ListBox-Eintrag zeigt, noch existiert?
    (In der Hilfe steht ja extra, dass die ListBox nicht der Owner wird und ich das Objekt somit selbst freigeben muss, falls der Eintrag gelöscht wird. Ist ja auch gut so, nur andersrum, wie ich den Eintrag da wegkriege, wenn ich das Objekt irgendwo anders lösche, wird nicht beschrieben.)

    Ich könnt ja im Destruktor der Klasse das Element aus der ListBox löschen lassen, aber dann müsste ich da ja dort nochmal die gesamte Listbox nach diesem Objekt durchsuchen, wenn es denn überhaupt in irgendner Liste steht. Ich würd das gern alles in ner "Refresh"-Funktion zusammenhaben und nur einmal die Liste nach Änderungen untersuchen.
    Ich könnt mir natürlich auch beim Löschen eines Objekts ne Liste erstellen, wo ich mir die Adressen der gelöschten Objekte merke und dann bei Aktualisieren der Liste prüfe, ob der Zeiger ne Adresse hat, die in der "Löschliste" steht.

    Aber das geht doch bestimmt noch irgendwie schöner, oder?



  • Ich habe das mal so gelöst, das ich die erzeugten Objekt Instanzen im Konstruktur in ein Set abgelegt habe und im Destruktor dann wieder daraus entfernt habe. Dann kann man einfach prüfen, ob die Objektinstanz noch existiert:
    Einfaches Beispiel:

    class myclass
    {
    public:
      mylcass(){Instances.insert(this);}
      ~myclass(){Instances.erase(this);}
    
      static bool exists(myclass *pobj)
      { return Instances.count(pobj) >0;}
    
    private:
      static std::unordered_set<myclass*> Instances;
    
    }
    

    Nun kann man, wenn man irgendwo her auf das Objekt zu greifen will, so prüfen, ob dieses noch existiert:

    if (myclass::exists(obj))
    {
      //Objekt existiert noch.
    }
    


  • Das Konservieren eines Zeigers über die Lebenszeit des referenzierten Objekts hinaus führt zu undefined behavior:
    https://kristerw.blogspot.de/2016/04/dangling-pointers-and-undefined-behavior.html
    https://trust-in-soft.com/dangling-pointer-indeterminate/

    Du solltest also in jedem Fall darauf achten, den Zeiger aus der Listbox zu entfernen, bevor das Objekt gelöscht wird.

    Ich würde die Listbox sowieso im virtuellen Modus verwenden (cf. TListBox::Style , TCustomListBox::OnData ), dann kannst du die Daten nach Belieben selbst verwalten, anstatt auf TStringList angewiesen zu sein.



  • ok danke euch beiden schonmal! 🙂
    Die Idee von Burkhi find ich gar nicht so schlecht, wobei es laut den Artikeln ja auch zu undefined behavior führen soll, da über die Count-Methode ja auch nen Vergleich gemacht wird.
    *Den Sinn davon versteh ich allerdings noch nicht ganz, warum der Wert des Pointers unbestimmt wird, wenn das referenzierte Objekt gelöscht wird.

    Once the memory is deallocated, the mapping is no longer guaranteed to exist. Use of the segment descriptor might now cause an exception, or the hardware addressing logic might return meaningless data.

    Ich greif ja nicht drauf zu, also warum darf ich nicht mit dem alten Wert als ID zum vergleichen arbeiten? Auch scheint mir das ja ein bisschen veraltet zu sein:*

    The reason for the standard to make this undefined behavior is that even simple operations on dangling pointers, such as assignment or comparison, may misbehave and result in exceptions or arbitrary values on some architectures.At least on architectures that existed when the first version of the standard was written.

    Finde ich jetzt nicht so wirklich nutzerfreundlich.. Aber dann bleibt mir ja eigtl nichts übrig, als den Eintrag doch im Destruktor rauszunehmen und die ganze ListBox zu durchsuchen.

    Nun noch ne kleine Frage zur virtuellen ListBox: Was ist denn der Vorteil daran? Ja ok, ich hab keine StringList mehr, aber letztlich speicher ich doch auch nur einzelne Strings mit nem index ab und ordne dem ein Objekt zu. Woanders hab ich gelesen, dass das bessere Performance liefert bei großen Listen, aber warum? Oder ist da nochwas, was ich übersehe?



  • Benutzerfreundlichkeit steht m. E. auf der Prioritätenliste der C- und C++-Standards relativ weit unten.

    drummi schrieb:

    Nun noch ne kleine Frage zur virtuellen ListBox: Was ist denn der Vorteil daran? Ja ok, ich hab keine StringList mehr, aber letztlich speicher ich doch auch nur einzelne Strings mit nem index ab und ordne dem ein Objekt zu.

    Weil du nicht mehr an die TStringList gebunden bist, könntest du dann z.B. auch eine zusätzliche Indirektion einführen, durch die sowohl das undefinierte Verhalten als auch der lineare Lookup vermieden werden können:

    class BetterStringList
    {
    public:
        struct Entry
        {
            String text;
            std::int64_t objectId;
        };
    
    private:
        std::vector<Entry> entries;
    
    public:
        ... //
    };
    
    class ObjectManager
    {
    private:
        std::int64_t nextObjId_ = 0;
        std::unordered_map<std::int64_t, TObject*> objects_;
    public:
        std::int64_t insert(TObject* obj)
        {
            objects_[nextObjId_] = obj;
            return nextObjId_++;
        }
        bool contains(std::int64_t id) const
        {
            return objects_.find(id) != objects_.end();
        }
        TObject* tryGet(std::int64_t id) const
        {
            std::unordered_map<std::int64_t, TObject*>::const_iterator it = objects_.find(id);
            return it != objects_.end()
                ? it->second
                : NULL;
        }
        ...
    };
    

    (sollte auch bcc32-kompatibel sein)

    Aber ehrlich gesagt fände ich es besser, wenn du die Objektverweise ganz simpel aus der Stringliste löschst, bevor sie ungültig werden.



  • Alles klar, werde mich mal damit auseinandersetzen. Danke für Eure Hilfe! 🙂



  • drummi schrieb:

    ok danke euch beiden schonmal! 🙂
    Die Idee von Burkhi find ich gar nicht so schlecht, wobei es laut den Artikeln ja auch zu undefined behavior führen soll, da über die Count-Methode ja auch nen Vergleich gemacht wird.....

    Es wird die übergebene Adresse geprüft, ob diese im Set vorhanden ist. Da der Destruktor diese ja automatisch aus dem Set entfernt, stellt dies kein Problem dar, wenn dann mit einer nicht mehr existierende Adresse geprüft wird, ob die Instanz noch existiert.

    Allerdings stimme ich Audacia zu, das es sauberer ist, das solche Verweise besser vorher aus solch einer Liste entfernt werden sollten, bevor die Instanz freigegeben wird. Letztendlich kann man ja auch seiner Klasse im Konstruktor einen Zeiger auf so eine Liste übergeben, aus der sie sich dann im Destruktor ganz von selbst austrägt.