Gegenseitige Referenz
-
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).
-
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.
-
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.
- mal hat man ein temporäres Objekt, das nach dem Statement wieder abgeräumt wird (und in seinem Destruktor vielleicht Weltfrieden bringt
-
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.
-
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

-
LordJaxom schrieb:
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

kommt darauf an, was du mit äquivalent meinst. jedenfalls führt die direkte initialisierung zum selben ergebnis. unterschiede könnten sich nur ergeben, wenn bei copy-initialisierung ein temporary erzeugt wird und das irgendwelche seiteneffekte zur folge hat. du könntest ja auch
A a( A( *new B("Fred", a) ) );schreiben

-
MSVC++2005 sagt sowohl zu
A a( A( *new B("Fred", a) ) );als auch zu
A a( *new B("Fred", a) );MSVC++2005 schrieb:
error C2065: 'a' : undeclared identifier

Nur die Copy-Initialisierung per Zuweisung wird akzeptiert.
-
stimmt, ja. da war ich wohl selbst ein wenig konfus.
camper schrieb:
ein bezeichner gilt als deklariert unmittelbar nach dem letzten deklarator (insbesondere vor seinem initialisierer).
ist ja nur die halbe wahrheit. der initialisierer ist ja selbst ein deklarator und regel lautet
3.3.1/1 The point of declaration for a name is immediately after its complete declarator (clause
and before its
initializer (if any), except as noted below.(für die diskussion unwesentliche absätze mit ausnahmen folgen)
-
wie kann man sich nur seitenweise darüber unterhalten wie man ein so schlechtes design umsetzen kann..
-
Eine Referenz ist intern ein derefrenzierter Zeiger, A* a; A& = *a;
struct B; struct A { B* b; B& my_B; A(B* b); }; struct B { A* a; A& my_A; B(A* a); }; A::A(B* b) : b(b), my_B(*b){ if (b) b->a = this; } B::B(A* a) : a(a), my_A(*a){ if (a) a->b = this; } int main() { A h(0); //Null-Zeiger struct B existiert noch nicht B m(&h);//Referenz auf h wird gebildet //und h erhält eine Referenz auf m von m. }mfg
-
Helmut S. schrieb:
Eine Referenz ist intern ein derefrenzierter Zeiger, A* a; A& = *a;
struct B; struct A { B* b; B& my_B; A(B* b); }; struct B { A* a; A& my_A; B(A* a); }; A::A(B* b) : b(b), my_B(*b){ if (b) b->a = this; } B::B(A* a) : a(a), my_A(*a){ if (a) a->b = this; } int main() { A h(0); //Null-Zeiger struct B existiert noch nicht B m(&h);//Referenz auf h wird gebildet //und h erhält eine Referenz auf m von m. }mfg
undefiniertes verhalten.
-
camper schrieb:
...undefiniertes verhalten.
Und das ist noch optimistisch betrachtet - ich würde auf eine Speicherschutzverletzung tippen:
Helmut S. schrieb:
...
... A::A(B* b) : b(b), my_B(*b)... // Hier wird (mit u.a. Code) 0 dereferenziert int main() { A h(0); ...mfg
OK, bei der Initialisierung einer Referenz muß der Compiler vielleicht nicht unbedingt dereferenzieren, aber alternativ wird er eben den hinter der Referenz stehenden Pointer auf "0" setzen .... was dann beim ersten Zugriff auf die Referenz den entsprechenden Fehler gibt.
Gruß,
Simon2.