Referenz auf Object einer Klasse zur Laufzeit halten



  • Habe folgenden Code

    class A{
       std::vector<Foo> vec_;
    };
    
    class B{
       B(const A& a) : a_{a}
    
       const A& a_;
       Foo& foo_
     public: 
       void update(...){
          foo_ = ....
       }
    };
    

    B soll einen Verweis auf immer genau ein bestimmtes Foo Objekt aus der Klasse B halten. Referenzen funktionieren nicht, da sie initialisiert werden müssen bei Objekt Konstruktion und ich aber erst mittels einer Methode den Verweis auf Foo setzen kann. Die Möglichkeit, dass der vector shared_ptr zu Foos hält scheidet auch aus, da dies design bedingt nicht funktioniert. Ich habs gerade mittels eines raw-pointers gelöst, aber ist das wirklich der beste Weg hier?



  • Sewing schrieb:

    Referenzen funktionieren nicht, da sie initialisiert werden müssen bei Objekt Konstruktion ...

    Nicht nur das.
    Die Referenz foo_ kann, solange die B-Instanz lebt, nur auf genau die Foo-Instanz verweisen, auf die sie initialisiert wurde. b.update (ein-anderes-Foo) biegt nicht die Referenz auf ein neues Foo um, sondern kopiert von diesem anderen Foo in das, was referenziert wird.

    Die Möglichkeit, dass der vector shared_ptr zu Foos hält scheidet auch aus, da dies design bedingt nicht funktioniert. Ich habs gerade mittels eines raw-pointers gelöst, aber ist das wirklich der beste Weg hier?

    Mit deinem (zugegebenermaßen erklärungsbedürftigen) Design versprichst du, dass die im B referenzierte A-Instanz const ist. D.h. mit Konstruktion eines B wird das übergebene A (inklusive enthaltendem Foo-Vektor) de facto eingefroren und darf nicht mehr verändert werden, solange das B lebt. Insofern ist das Speichern eines Raw-Pointers auf ein Foo ein gangbarer und sicherer Weg - vorausgesetzt, das gegebene Versprechen wird eingehalten und das A wird nicht doch hintenrum von irgendwem verändert. Falls doch, und jemand sorgt (z.B. durch resize) dafür, dass der Vector seine Elemente an einen anderen Platz verschiebt, kommst du in Teufels Küche.

    Der "beste" Weg wäre vermutlich, das Design zu überdenken.



  • danke für dein feedback! Ja ichwüsste gerne wie ich das ändern kann.

    Es geht darum, dass in der Klasse A sequentielle Daten stehen (deshalb der vector) und ich möchte auf eine Nutzeeingabe hin ein bestimmtes Elements im Vector nehmen und in B (eine Visualisierungsklasse) darstellen. In Klasse B wird permanent eine draw() methode aufgerufen (es handelt sich um eine OpenGL Anwendung) und ich möchte nicht bei jeder Nutzereingabe die Gesamtheit der Daten aus einerm Foo Objekt in das B Objekt kopieren.

    Was wäre denn hier eine Möglichkeit?



  • Ach so. Also, du musst in update() nur das Foo übergeben das gemalt werden soll. Eine Kenntnis von dem A-Objekt ist in B dafür nicht nötig, außer es werden noch andere Informationen aus dem A herausgeholt (Beschriftungen oder ähnliches).

    Du hast bei einem vector<Foo> das grundsätzliche Problem, dass der Vector von Zeit zu Zeit seinen ganzen Inhalt woandershin verschiebt, wenn er verlängert wird, weil neue Datensätze angefügt werden. Alle Raw-Pointer auf Foo-Objekte, die du bis dahin gezogen hast, werden dann auf einen Schlag ungültig, ohne dass du das erkennen kannst. Das gibt garantiert irgendwann Ärger.

    Eigentlich, das hast du völlig richtig erkannt, ist das hier ein Fall für shared_ptr<Foo>. Man sollte drüber nachdenken, A so zu definieren:

    class A
    {
    std::vector<std::shared_ptr<Foo>> theFoos;
    };
    

    Außerdem sollte man über einen anderen Container nachdenken, irgendwas Listenartiges, was beim Verlängern die alten Elemente am Platz lässt. Irgendwann macht dir sonst das push_back die Performance kaputt.

    Eine andere sichere Methode ist das Reinkopieren des Foo in das B, was allerdings je nach Größe mit einer Performancestrafe belegt sein kann.

    Unterm Strich: shared_ptr ist wirklich das beste.



  • danke für deine ausführliche Antwort.

    Ich hatte das beispiel oben etwas vereeinfacht, tatsächlich habe ich als Container

    using Recordings = std::unordered_map<size_t, Foo>;
    

    Pointer invalidation ist hier also kein Problem. Gleichzeitig konstruiere ich allerdings Elemente in der map mittels

    class Foo{
       Foo(std::string s){}
    };
    
      auto [iter, success] { recordings_.try_emplace(snapshot.id_, "Test") };
    

    und baue darauf, dass Foos nur erzeugt werden, wenn derkey noch nicht vorhanden ist in der map. wenn ich nun schreiben würde

    auto [iter, success] { recordings_.try_emplace(snapshot.id_, std::make_shared<Foo>("Test") };
    

    dann erzeugt er mir in jedem fall ein Foo object, wasgewissermaßen mein Unterfagen der bedingten Objekterschaffung torpediert 😕



  • Sewing schrieb:

    ... und baue darauf, dass Foos nur erzeugt werden, wenn derkey noch nicht vorhanden ist in der map.

    War das nicht im anderen Thread das Thema, dass die Argumente von try_emplace vor dem Aufruf ausgewertet werden, was auf jeden Fall zur Bildung eines namenlosen Foo-Objekts führt? Dann ist es an dieser Stelle Jacke wie Hose, ob du vorher noch einen shared_ptr-Wrapper drumherum packst oder nicht.

    Außerdem, wie soll denn das passieren, dass ein Schnappschuss mehrfach in die Map eingefügt wird? Bei ner vernünftigen Ablaufprogrammierung (Foto machen - ID zuordnen - einfügen - Foto machen - ID zuordnen - einfügen ...) ist das doch gar nicht möglich. Außer wenn die IDs zufällig mehrdeutig sind, und dann eignen sie sich nicht dafür. D.h. try_emplace wird immer einfügen, und natürlich muss das Foo dafür erzeugt werden.

    Wie auch immer, entscheide selber, wofür es die stärkeren Argumente gibt. Für eine fragwürdige emplace-oder-auch-nicht-Logik oder für sichere Pointer.



  • ja genau, ich habe es nur gelöst, in dem ich die Foo Objekte in eine Struct wrappe und mit einem string initialisiere, der bei try_emplace übergeben wird. somit werden foos auch nur wirklich dann erschaffen, wenn der key noch nicht vorhanden ist.

    was das mehrfache einfügen eines keys betrifft:

    man kann sich durch eine sequenz von aufnahmen klicken und jede aufnahme muss beim ersten Mal gerendert werden. Und um das wirklich nur einmal zu tun, verwende icheben try_amplace



  • //egal



  • Sewing schrieb:

    ja genau, ich habe es nur gelöst, in dem ich die Foo Objekte in eine Struct wrappe und mit einem string initialisiere, der bei try_emplace übergeben wird. somit werden foos auch nur wirklich dann erschaffen, wenn der key noch nicht vorhanden ist.

    Naja, es wird ein const char[5] erzeugt (string Literal).

    Ich finde es schöner, nichts zu erzeugen und eine if-Abfrage auf den Key zu machen.



  • das ist schon klar, aber besser als nen ganzes Objekt ; )



  • Sewing schrieb:

    das ist schon klar, aber besser als nen ganzes Objekt ; )

    Wieso willst du keine if-Abfrage auf den Key davorsetzen?



  • weil ich dann nicht verstehe, wieso try_emplace überhaupt existtiert


Anmelden zum Antworten