In GCC 4.2 bekomme ich diese Nachricht:
%Vor%Wenn ich das "private" von B entferne, bekomme ich die erwartete Ausgabe:
%Vor%Meine Frage ist: Warum macht eine Methode, die nicht private genannt wird, eine Änderung, ob dieser Code kompiliert? Ist das Standard-Mandat? Gibt es eine Problemumgehung?
Der wichtige Ausdruck im aktuellen Standard (C ++ 03) scheint in § 8.5.3 zu sein, der erklärt, wie Referenzen initialisiert werden (In diesen Anführungszeichen ist T1
der Typ der zu initialisierenden Referenz und T2
ist der Typ des Initialisierungsausdrucks).
Wenn der Initialization-Ausdruck ein rvalue ist, mit
T2
ein class-Typ und "cv1 T1
" mit "cv2 T2
" referenzkompatibel ist, wird der Verweis auf eine der folgenden Arten gebunden (die Auswahl ist implementierungsdefiniert):- Die Referenz ist an das durch den rvalue (siehe 3.10) dargestellte Objekt oder an ein Unterobjekt innerhalb dieses Objekts gebunden.
- Ein temporäres Objekt vom Typ "
cv1 T2
" [sic] wird erstellt, und ein Konstruktor wird aufgerufen, um das gesamte rvalue-Objekt in das temporäre Objekt zu kopieren. Die Referenz ist an das temporäre Objekt oder an ein Unterobjekt innerhalb des temporären Objekts gebunden.Der Konstruktor, der zum Erstellen der Kopie verwendet wird, muss aufrufbar sein, unabhängig davon, ob die Kopie tatsächlich erstellt wurde oder nicht.
Selbst wenn die Implementierung den Verweis direkt an das temporäre Objekt bindet, muss der Kopierkonstruktor zugänglich sein.
Beachten Sie, dass dies in C ++ 0x geändert wird, nach der Auflösung von CWG defekt 391 . Die neue Sprache lautet (N3092 §8.5.3):
Andernfalls, wenn
istT2
ein Klassentyp und- Der Initialisierungsausdruck ist ein rvalue und "
cv1 T1
" ist referenzkompatibel mit "cv2 T2
",-
T1
ist nicht referenzbezogen aufT2
und der Initialisiererausdruck kann implizit in einen rvalue vom Typ "cv3 T3"
" konvertiert werden (diese Konvertierung wird durch Aufzählung der anwendbaren Konvertierungsfunktionen ausgewählt (13.3.1.6) und Auswahl der besten durch Überladungsauflösung (13.3)),dann ist die Referenz im ersten Fall an den Initialisiererausdruck rvalue und im zweiten Fall an das Objekt gebunden, das das Ergebnis der Konvertierung ist (oder in jedem Fall an das entsprechende Unterobjekt der Basisklasse des Objekts).
Der erste Fall wird angewendet und die Referenz wird direkt an den Initialisierungsausdruck gebunden.
Sie verwenden also 'copy-initialization':
8.5 / 11 Initialisierer
Die Form der Initialisierung (unter Verwendung von Klammern oder =) ist in der Regel unbedeutend, aber egal wann Die initialisierte Entität hat a Klassentyp; siehe unten. ...
Die Initialisierung in Argumentübergabe, Funktionsrückgabe, eine Ausnahme werfen (15.1), Handhabung eine Ausnahme (15.3), und In Klammern gesetzte Initialisierungslisten (8.5.1) wird Kopierinitialisierung genannt und entspricht dem Formular
%Vor%Die Initialisierung, die in new auftritt Ausdrücke (5.3.4), static_cast Ausdrücke (5.2.9), funktional Notationstypkonvertierungen (5.2.3) und Basis- und Elementinitialisierer (12.6.2) heißt Direkt-Initialisierung und ist entspricht dem Formular
%Vor%
In 13.3.1.3 "Initialisierung durch Konstruktor" lauten die Überladungen für den ausgewählten Konstruktor:
Wenn Objekte des Klassentyps direkt initialisiert (8.5) oder aus einem Ausdruck des gleichen oder eines abgeleiteten Klassentyps (8.5) initialisiert werden, wählt die Überladungsauflösung den Konstruktor aus. Für die direkte Initialisierung sind die Kandidatenfunktionen alle Konstruktoren der Klasse des Objekts, das initialisiert wird. Für die Kopierinitialisierung sind die Kandidatenfunktionen alle Konvertierungskonstruktoren (12.3.1) dieser Klasse.
Daher muss der Kopierkonstruktor für die Kopierinitialisierung verfügbar sein. Der Compiler darf jedoch die Kopie "optimieren":
12.2 / 1 Temporäre Objekte
Auch wenn die Erstellung des temporären Objekts vermieden wird (12.8), müssen alle semantischen Einschränkungen beachtet werden, so als ob das temporäre Objekt erstellt wurde. [Beispiel: Selbst wenn der Kopierkonstruktor nicht aufgerufen wird, müssen alle semantischen Einschränkungen, wie etwa die Zugänglichkeit (Abschnitt 11), erfüllt sein. ]
Sie können den gewünschten Effekt erhalten, indem Sie die Kopierinitialisierung vermeiden und die direkte Initialisierung verwenden:
%Vor%Hinweis:
Da neuere Versionen von GCC anscheinend ein anderes Verhalten haben, dachte ich, ich würde diese Notiz veröffentlichen, die den Unterschied beheben könnte (wobei beide Verhaltensweisen immer noch den Standards entsprechen):
8.5.3 / 5 Referenzen sagt:
Wenn der Initialisierungsausdruck ein R-Wert ist, mit T2 ein Klassentyp und "cv1 T1" mit "cv2 T2" referenzkompatibel ist, wird die Referenz auf eine der folgenden Arten gebunden (die Auswahl ist implementierungsdefiniert) :
Die Referenz ist an das Objekt gebunden, das durch den rvalue (siehe 3.10) oder ein Unterobjekt darin repräsentiert wird dieses Objekt.
Ein temporärer Typ vom Typ "cv1 T2" [sic] wird erstellt, und ein Konstruktor wird aufgerufen, um das gesamte rvalue-Objekt in das temporäre Objekt zu kopieren. Die Referenz ist an das temporäre Objekt oder an ein Unterobjekt innerhalb des temporären Objekts gebunden.
Der Konstruktor, der zum Erstellen der Kopie verwendet wird, muss aufrufbar sein, unabhängig davon, ob die Kopie tatsächlich erstellt wurde oder nicht.
Ich habe ursprünglich den letzten Satz gelesen ("der Konstruktor, der verwendet wird ..."), der auf beide Optionen angewendet wird, aber vielleicht sollte er nur als Option für die Option seconds gelesen werden - oder zumindest so ist der GCC Betreuer lesen es.
Ich bin mir nicht sicher, ob dies zwischen dem unterschiedlichen Verhalten der GCC-Versionen (Kommentare sind willkommen) geschieht. Wir erreichen definitiv die Grenzen meiner Sprachlehrerfähigkeiten ...
Ich denke, es ist in der Tat ein Compiler-Fehler, gcc scheint zu denken, dass das eine Kopierinitialisierung ist. Verwenden Sie stattdessen direkte Initialisierung:
%Vor%Der Aufruf des Kopierkonstruktors in der Kopierinitialisierung wird immer weg optimiert (eine Kopie der Kopie) und muss dann nicht verfügbar sein.