Gegenseitige Referenz



  • hI

    Kann man Referenzen gegenseitig machen? So das A B kennt und B A?

    struct B;
    
    struct A
    {
    ...daten
      B& my_b;
    };
    
    struct B
    {
    ...daten
      A& my_a;
    };
    

    Greez 🙂



  • Hallo

    ja, und zwar genaus so wie du es gezeigt hast.

    bis bald
    akari



  • 🙂 Danke 🙂



  • akari schrieb:

    ja, und zwar genaus so wie du es gezeigt hast.

    Im ernst? Ich habs jetzt nicht ausprobiert, aber laut meiner Erfahrung geht das nicht. Weil die Objektgrößen von B in Struct A nicht bekannt sind. Das geht nur mit Pointern... aber nicht mit Referenzen.



  • Hallo

    doch geht mit Referenzen genauso wie mit Pointern.
    Und ich habs auch extra ausprobiert.

    /Edit : Das Problem dürfte eher sein, das die Referenzen ja schon mit dem jeweiligen Konstruktor belegt werden müßen. Das heißt für eine Instanz von A braucht man eine bereits vorhandene Instanz von B, und für eine Instanz für B braucht man eine bereits vorhandene Instanz von A... also wäre in diesem Fall Pointer angebrachter.

    bis bald
    akari



  • akari schrieb:

    Hallo
    ...Das Problem dürfte eher sein, das die Referenzen ja schon mit dem jeweiligen Konstruktor belegt werden müßen. ...

    Eben. Deswegen sage ich: Nein, geht nicht !

    Liegt einfach daran, dass man mit einer Referenz eben doch etwas Bestimmtes ausdrückt ("Dieses Ding kennst Du und es muss da sein") und mit einem Pointer eben etwas Anderes ("Dieses Ding kannst Du kennen, wenn es da ist").
    Deswegen glaube ich auch nicht, dass Du es ausprobiert hast 😉 ... jedenfalls nicht, dass Du ein A oder ein B angelegt hast (geschweige denn, beide).

    Gruß,

    Simon2.



  • Es geht auch mit Referenzen.
    Dazu muss man noch einen Konstruktor definieren, und die Referenz in der Initialisierungsliste initialisieren.
    Da die Definition des Konstruktors in der *.cpp erst nach der Definition der Klassen oder der structs erfolgen kann, kennt der Compiler dann auch beide Definitionen.

    DJohn



  • @DJohn:
    Da Du akaris Edit nicht verinnerlicht zu haben scheinst, versuche doch einfach mal selbst, zwei Klassen zu schreiben die je eine Referenz auf die andere Klasse enthalten und diese auch beide zu instanziieren. Das Ergebnis führst Du uns dann mal vor, das interessiert mich nämlich brennend. 😉



  • LordJaxom schrieb:

    @DJohn:
    Da Du akaris Edit nicht verinnerlicht zu haben scheinst, versuche doch einfach mal selbst, zwei Klassen zu schreiben die je eine Referenz auf die andere Klasse enthalten und diese auch beide zu instanziieren. Das Ergebnis führst Du uns dann mal vor, das interessiert mich nämlich brennend. 😉

    #include <string>
    #include <iostream>
    using namespace std;
    struct B;
    struct A {
    	A(B& b);
    	B& b_;
    	string name_;
    };
    
    struct B {
    	B(const char* n, A& a);
    	A& a_;
    	string name_;
    };
    
    A::A(B& b) : b_(b) {}
    B::B(const char* n, A& a) : a_(a), name_(n) {}
    
    int main() {
    	A a = *new B("Fred", a);
    	a.name_ = "Hallo";
    	cout << a.name_ << " " << a.b_.name_ << endl;
    	cout << a.name_ << " " << a.b_.name_ << " " << a.b_.a_.name_ << endl;
    }
    

    Ich denke solange man im Ctor von B das A-Objekt nicht verwendet (ist ja noch nicht konstruiert) sondern nur die Referenz speichert, ist das Ganze ok (mal von dem Mem-Leak abgesehen).


  • Mod

    LordJaxom schrieb:

    @DJohn:
    Da Du akaris Edit nicht verinnerlicht zu haben scheinst, versuche doch einfach mal selbst, zwei Klassen zu schreiben die je eine Referenz auf die andere Klasse enthalten und diese auch beide zu instanziieren. Das Ergebnis führst Du uns dann mal vor, das interessiert mich nämlich brennend. 😉

    gegenseitiges referenzieren mit echten referenzen dürfte im allgemeinen keine gute idee sein - hauptsächlich deshalb, weil es schwierig ist, geeignete invarianten dafür zu finden. möglich ist es allerdings schon (wenn es auch eines etwas weniger präzise definierten teil der sprache betrifft).

    struct A;
    struct B
    {
        A& a;
        B();
        ~B();
    };
    struct A
    {
        B& b;
        A(B&);
    };
    
    A::A(B& b) : b(b) {}
    B::B() : a(*new A(*this)) {}
    B::~B() { delete &a; }
    

    eine andere variante wäre, zunächst nur geeigneten speicher anzufordern (etwa über einen allocator) und dann eine referenz auf diesen noch nicht initialisierten speicher zu formen.
    siehe auch
    Initialization of class from self
    References may only bind to “valid” objects



  • Ojeojeoje 😉

    @HumeSikkins:
    Das funktioniert aber doch nur, weil der Compiler die Zuweisung zu a bei der Initialisierung zur Konstruktion optimieren darf? Was mich zu der Frage bringt, darf oder muss er das in dem Fall, denn folgendes geht nicht (VC++2005), da a während der Konstruktion von a unbekannt ist?

    A a( *new B("Fred", a) );
    

    @camper:
    Das ist mir schon einleuchtender, wenngleich ich Dir auch zustimme: Das sieht nicht aus als wäre es eine gute Idee 😃



  • Ja, ich hatte das EDIT von akari übersehen. Das es theoretisch möglich ist wurde schon gezeigt und ich finde auch kein sinnvolles Anwendungsbeispiel.

    Hier ist meine Version, wobei man sich dann fragt, wozu bref gebraucht wird.

    struct A;
    struct B{
     B(A&);
     A& aref;
    };
    struct A{
    A()
    B b;
    B& bref;
    };
    
    B::B(A& a):aref(a){}
    A::A():b(*this),bref(b){}
    
    int main(){
     A a;
    }
    

    DJohn



  • LordJaxom schrieb:

    Ojeojeoje 😉

    @HumeSikkins:
    Das funktioniert aber doch nur, weil der Compiler die Zuweisung zu a bei der Initialisierung zur Konstruktion optimieren darf?

    Achtung, hier ist keine Zuweisung am Werk. Das ist Copy-Initialisierung, ein entscheidener Unterschied.
    http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=copyvsdirect#Answ



  • HumeSikkins schrieb:

    ...

    ...
    int main() {
    	A a = *new B("Fred", a);
    ...
    

    ...

    Hi,

    ich sehe aber nicht, dass a auf dasselbe Objekt zeigt wie B::a, oder ?
    Mal ganz davon abgesehen, dass ich nicht weiß, woher in diesem Beispiel der B-Ctor das "a" auflösen kann (wie schon vom Lord angemerkt):
    - IIRC "verkürzt" der Compiler die Kombination "DefCtor+operator=()" in solch einem Fall zum CpyCtor (wobei mich wundert, warum das o.a. läuft und Jaxoms Code nicht).
    - Auf jeden Fall aber KOPIERT er das Ergebnis vom new(...) in a mit einem automatisch erzeugten Cpy-Ctor, der natürlich auch den String mitkopiert.

    Aber da kämst Du natürlich "raus" mit

    ...
    int main() {
    	A& a = *new B("Fred", a);
    ...
    

    😉

    Gruß,

    Simon2.


  • Mod

    ein bezeichner gilt als deklariert unmittelbar nach dem letzten deklarator (insbesondere vor seinem initialisierer). in

    A a = *new B("Fred", a);
    

    ist der parameter a im new tatsächlich der speicherbereich des hier deklarierten objektes a - offensichtlich ist hier aber das konstruktorargument kein lebendes objekt sondern eben nur ein speicherbereich mit ausreichend platz und korrektem alignment. durch das derefenzieren erhalten wir ein lvalue vom typ B, und da A einen geeigneten konstruktor hat, der ein B& nimmt, wird entweder
    - ein temporäres objekt mittels dieses konstruktors erstellt und anschließend a per copy-ctor durch dieses temporary konstruiert, oder
    - a wird direkt durch den erwähnten konstruktor erstellt
    welche der beiden möglichkeiten genutzt wird, ist dem compiler freigestellt.

    Simon2 schrieb:

    ...
    int main() {
    	A& a = *new B("Fred", a);
    ...
    

    B besitzt keinen geeigneten konvertierungsoperator, der ein A& (oder etwas in A& konvertierbares) liefert. damit ist diese initialisierung unmöglich, da das initialisieren einer referenz auf nicht konstantes T keine temporären objekte erzeugt.



  • camper schrieb:

    ein bezeichner gilt als deklariert unmittelbar nach dem letzten deklarator (insbesondere vor seinem initialisierer). ....

    💡 Aha, das wusste ich noch gar nicht, dass der Bezeichner schon gültig ist.

    camper schrieb:

    ...
    - a wird direkt durch den erwähnten konstruktor erstellt
    welche der beiden möglichkeiten genutzt wird, ist dem compiler freigestellt.....

    💡 Wusste ich auch noch nicht. Ich dachte der Compilerhersteller sei zu der anderen Variante verpflichtet.
    Finde ich auch seltsam, dass das erlaubt ist, weil das Verhalten doch anders ist

    • mal hat man ein temporäres Objekt, das nach dem Statement wieder abgeräumt wird (und in seinem Destruktor vielleicht Weltfrieden bringt 😉 ),
    • mal wird die Lebenszeit des Objekts an den scope von a gebunden (das uns in einer Endlosschleife in alle Ewigkeiten schmoren lässt).

    Oder gilt diese Spezialfall nur für das "new" ? Dann wäre aber trotzdem der Scope des erzeugten Objektes anders als der von a (nämlich länger)...

    Nun gut, man lernt nie aus !

    Simon2 schrieb:

    ...
    int main() {
    	A& a = *new B("Fred", a);
    ...
    

    ...damit ist diese initialisierung unmöglich, da das initialisieren einer referenz auf nicht konstantes T keine temporären objekte erzeugt.[/quote]
    Da bin ich aber beruhigt. 🙂
    Danke für die Aufklärung.

    Gruß,

    Simon2.



  • Oh nö 😮

    Dann lass ich das lieber.
    Ihr meint ich sollte das besser nur mit Zeigern machen?



  • fR4gg3R schrieb:

    ...Ihr meint ich sollte das besser nur mit Zeigern machen?

    Die spannende Frage lautet: Was machen ?

    Wie gesagt: Wenn 2 Objekte (selbe Klasse oder unterschiedliche Klasse spielt keine Rolle) einander "kennen", muss man sich über die Rahmenbedingungen in Klaren sein:
    - Was sollen sie miteinander anfangen können ?
    - Gehört das eine dem anderen ?
    - Kommt eines ohne das andere aus oder braucht es das andere zwingend ?
    - Wer erzeugt die Objekte ? (Der "Erzeuger" muß halt vorher da sein)
    - .....

    Wenn Du einfach nur ein wenig mit rumspielen willst (was eine seeeehr schöne und auch lehrreiche Beschäftigung ist 😋 ), solltest Du es einfach mal probieren mit:

    class BinEinDu;
    class B;
    
    class A : public BinEinDu {
        B habeDich;
        B& braucheDich;
        B* kenneDich;
        ...
    };
    

    😉
    Einfach mal ein paar Member-Funktionen von A schreiben, die mit den Teilen rumhantieren, sie zurückgeben, ...
    Da sagt einem der Compiler eigentlich schon eine Menge, was geht und was nicht.
    Und wenn Dir langweilig ist, streust Du noch ein paar consts ein (in die Deklaration der "...Dich"'s und/oder der Memberfunktionen.
    Da kann man sich gut und gerne ein paar Tage bis Wochen beschäftigen mit.

    Gruß,

    Simon2.



  • @HumeSikkins:

    Danke für den Link, aber das war mir schon klar, dass das entweder eine Copyconstruction aus einem Temporary oder eine Construction unter eliminierung des Temporary sein muss (falsch ausgedrückt).

    Was mir jedoch nicht klar war, war dass man auf diesem Wege das Temporary eliminieren UND auf den zu initialisierenden Bezeichner zugreifen kann. Wenn ich das richtig verstehe kann man die Zeile

    A a = *new B("Fred", a);
    

    weder durch die direkte Initialisierung

    A a( *new B("Fred", a) );
    

    noch durch die Copy-Initialisierung via Temporary

    A temp( *new B("Fred", <nix da>) );
    A a( temp );
    

    äquivalent darstellen. Ich dachte immer die Copy-Initialisierung ist zu mindestens einem dieser beiden äquivalent. Aber man lernt bekanntlich nie aus 😉



  • Klassenbeziehungen, wie süüs 🙂

    Na dann üb ich mal ein bisschen, mit "brauchDich", "habDichLieb" und soweiter 😘

    Danke 👍

    Greez 🙂


Anmelden zum Antworten