Ich habe gerade festgestellt, dass dieses Programm kompiliert und ausgeführt wird (gcc Version 4.4.5 / Ubuntu):
%Vor%Ich frage mich, warum auf der Erde das überhaupt zustande kommt. Ich gehe davon aus, dass (genau wie in Java) Argumente ausgewertet werden, bevor die Methode / Konstruktor aufgerufen wird, also vermute ich, dass dieser Fall durch einen "Spezialfall" in der Sprachspezifikation abgedeckt sein muss?
Fragen:
EDIT 1: Ich habe gerade gemerkt, dass ich sogar int i = i;
EDIT 2: Auch mit -Wall
und -pedantic
wird über Test a(a);
nicht geklagt.
EDIT 3: Wenn ich eine Methode hinzufüge
%Vor% Ich kann sogar Test a(method(a));
ohne Warnungen machen.
Der Grund dafür ist, dass die Regeln angeben, dass ein Bezeichnerbereich unmittelbar nach dem Bezeichner beginnt. In dem Fall
%Vor%die RHS ich ist "nach" der LHS, so dass ich im Umfang bin. Das ist nicht immer schlecht:
%Vor%, weil eine Variable adressiert werden kann, ohne dass ihr Wert verwendet wird. Im Falle des Copy-Konstruktors des OPs kann kein Fehler leicht angegeben werden, da das Binden eines Verweises auf eine Variable nicht erfordert, dass die Variable initialisiert wird: Dies entspricht dem Entnehmen der Adresse einer Variablen. Ein legitimer Konstruktor könnte sein:
%Vor%Wenn Sie sehen, dass das Argument nur adressiert ist, wird sein Wert nicht verwendet. In diesem Fall könnte eine Selbstreferenz sinnvoll sein: Das Ende einer Liste ist durch eine Selbstreferenz gegeben. In der Tat, wenn Sie den Typ von "nächsten" zu einer Referenz ändern, gibt es wenig Auswahl, da Sie NULL nicht so leicht wie für einen Zeiger verwenden können.
Wie üblich ist die Frage rückwärts. Die Frage ist nicht, warum eine Initialisierung einer Variablen auf sich selbst verweisen kann, die Frage ist, warum sie sich nicht darauf beziehen kann. [In Felix ist das möglich]. Insbesondere für Typen im Gegensatz zu Variablen ist die fehlende Fähigkeit, eine Referenz weiterzuleiten, extrem gebrochen, da sie verhindert, dass rekursive Typen anders definiert werden als durch Verwendung unvollständiger Typen, was in C ausreichend ist, aber nicht in C ++ aufgrund der Existenz von Vorlagen.
Ich habe keine Ahnung, wie das mit der Spezifikation zusammenhängt, aber so sehe ich es:
Wenn Sie Test a(a);
ausführen, wird Speicherplatz für a
auf dem Stapel zugewiesen. Daher ist der Speicherort von a
im Speicher dem Compiler zu Beginn von main
bekannt. Wenn der Konstruktor aufgerufen wird (der Speicher wird natürlich vorher zugewiesen), wird der korrekte this
-Zeiger an ihn übergeben, weil er bekannt ist.
Wenn du Test *b = new Test(*b);
machst, musst du es als zwei Schritte betrachten. Zuerst wird das Objekt zugewiesen und konstruiert, und dann wird der Zeiger auf es zugewiesen b
. Der Grund, warum Sie die Nachricht erhalten, ist, dass Sie im Wesentlichen einen nicht initialisierten Zeiger auf den Konstruktor übergeben und ihn mit dem tatsächlichen this
-Zeiger des Objekts vergleichen (der schließlich b
zugewiesen wird, aber nicht bevor der Konstruktor beendet wird).
Der zweite, in dem Sie new
verwenden, ist eigentlich einfacher zu verstehen; Was Sie dort aufrufen, ist genau dasselbe wie:
und Sie führen tatsächlich eine ungültige Dereferenzierung durch. Versuchen Sie, einen << &other <<
zu Ihren cout
-Zeilen im Konstruktor hinzuzufügen, und machen Sie das
, um zu sehen, dass Sie den Wert übergeben, den ein Zeiger auf dem Stapel erhalten hat. Wenn nicht explizit initialisiert, ist das nicht definiert. Aber selbst wenn Sie es nicht mit einer Art (un) normalem Standard initialisieren, wird es sich von dem Rückgabewert von new
unterscheiden, wie Sie herausgefunden haben.
Stellen Sie sich zunächst einmal vor, dass es sich um eine in-place new
handelt. Test a
ist eine lokale Variable, nicht ein Zeiger, sie lebt auf dem Stapel und daher ist ihr Speicherort immer gut definiert - dies ist sehr unterschiedlich zu einem Zeiger, Test *b
, der, wenn er nicht explizit mit einem gültigen Ort initialisiert wird, hängt .
Wenn Sie Ihre erste Instanziierung schreiben wie:
%Vor%es wird klarer, was Sie dort aufrufen.
Ich kenne keine Möglichkeit, den Compiler von dieser Art der Selbstinitialisierung aus dem Nichts durch den Kopierkonstruktor abhalten zu lassen (oder gar zu warnen).
Der erste Fall ist (vielleicht) von 3,8 / 6 abgedeckt:
vor der Lebensdauer eines Objekts begann aber nach der Lagerung die Das Objekt wird besetzt sein zugeordnet oder nach der Lebenszeit eines Objekt ist beendet und vor der Speicher, den das Objekt belegt hat wiederverwendet oder freigegeben, jeder lvalue welcher bezieht sich auf das ursprüngliche Objekt möglicherweise verwendet, aber nur in begrenztem Umfang. So ein Lvalue bezieht sich auf zugewiesenen Speicher (3.7.3.2) und unter Verwendung der Eigenschaften von der Wert, der nicht von seiner abhängt Wert ist wohldefiniert.
Da Sie nur die Adresse von a
(und other
, die an a
gebunden ist) vor dem Anfang ihrer Lebensdauer verwenden, denke ich, dass Sie gut sind: Lesen Sie den Rest dieses Absatzes für die detaillierten Regeln.
Beachten Sie jedoch, dass 8.3.2 / 4 besagt: "Eine Referenz soll initialisiert werden, um auf ein gültiges Objekt oder eine gültige Funktion zu verweisen." Es gibt eine Frage (als ein Fehlerbericht über den Standard), was "gültig" in diesem Kontext bedeutet, also können Sie möglicherweise den Parameter other
nicht an den unstrukturierten (und daher "ungültigen"?)% Co_de% binden.
Also, ich bin unsicher, was der Standard tatsächlich hier sagt - ich kann einen Lvalue verwenden, aber nicht an eine Referenz binden, in diesem Fall ist a
nicht gut, Das Übergeben eines Zeigers an a
wäre in Ordnung, solange es nur in den von 3.8 / 5 zugelassenen Möglichkeiten verwendet wird.
Im Falle von a
verwenden Sie den Wert vor der Initialisierung (weil Sie ihn dereferenzieren, und auch, wenn Sie so weit gekommen sind, wäre b
der Wert von &other
). Das ist eindeutig nicht gut.
Wie immer in C ++, kompiliert es, weil es keine Verletzung von Spracheinschränkungen ist und der Standard nicht explizit eine Diagnose erfordert. Stellen Sie sich die Verdrehungen vor, die die Spezifikation durchlaufen müsste, um eine Diagnose zu berechtigen, wenn ein Objekt in seiner eigenen Initialisierung ungültig verwendet wird, und stellen Sie sich die Datenflussanalyse vor, die ein Compiler möglicherweise durchführen muss, um komplexe Fälle zu identifizieren möglich zur Kompilierzeit, wenn der Zeiger durch eine extern definierte Funktion geschmuggelt wird). Es ist einfacher, es als undefiniertes Verhalten zu belassen, es sei denn jemand hat wirklich gute Vorschläge für eine neue Spezifikationssprache ;-)
Ich kenne den Spezifikationsverweis nicht, aber ich weiß, dass der Zugriff auf einen nicht initialisierten Zeiger immer zu undefiniertem Verhalten führt.
Wenn ich Ihren Code in Visual C ++ kompiliere, bekomme ich:
test.cpp (20): Warnung C4700: nicht initialisierte lokale Variable 'b' verwendet
Tags und Links c++ copy-constructor