Referenzinitialisierung
Bindet eine Referenz an ein Objekt.
Inhalt |
[bearbeiten] Syntax
[bearbeiten] Nicht-Listen-Initialisierung
T & ref = target ;T |
(1) | ||||||||
T && ref = target ;T |
(2) | (seit C++11) | |||||||
func-refpar ( target ) |
(3) | ||||||||
return target ; |
(4) | (innerhalb der Definition von func-refret ) | |||||||
Class::Class(...) : ref-member ( target ) { ... } |
(5) | (innerhalb der Definition von Class ) | |||||||
[bearbeiten] Gewöhnliche Listen-Initialisierung (seit C++11)
T & ref = { arg1, arg2, ... };T |
(1) | ||||||||
T && ref = { arg1, arg2, ... };T |
(2) | ||||||||
func-refpar ({ arg1, arg2, ... }); |
(3) | ||||||||
[bearbeiten] Benannte Listen-Initialisierung (seit C++20)
T & ref = {.des1 = arg1 , .des2 { arg2 } ... };T |
(1) | ||||||||
T && ref = {.des1 = arg1 , .des2 { arg2 } ... };T |
(2) | ||||||||
func-refpar ({.des1 = arg1 , .des2 { arg2 } ... }); |
(3) | ||||||||
Eine Referenz auf T kann mit einem Objekt vom Typ T, einer Funktion vom Typ T oder einem Objekt, das implizit in T konvertierbar ist, initialisiert werden. Nach der Initialisierung kann eine Referenz nicht neu zugewiesen werden (geändert werden), um auf ein anderes Objekt zu verweisen.
Referenzen werden in den folgenden Situationen initialisiert
[bearbeiten] Erklärung
| T | - | der Referenztyp |
| ref | - | die zu initialisierende Referenzvariable |
| target | - | der verwendete Initialisiererausdruck |
| func-refpar | - | eine Funktion mit einem Parameter vom Referenztyp (T & oder T &&(seit C++11)) |
| func-refret | - | eine Funktion, deren Rückgabetyp ein Referenztyp ist (T & oder T &&(seit C++11)) |
| Klasse | - | ein Klassename |
| ref-member | - | ein Nicht-Statische-Datenmember vom Referenztyp (T & oder T &&(seit C++11)) von Class |
| des1, des2, ... | - | Deskriptoren |
| arg1, arg2, ... | - | die Initialisierer in Initialisierungslisten |
[bearbeiten] Definitionen
Für zwei Typen T1 und T2
- Gegeben die cv-unqualifizierten Versionen von
T1undT2alsU1bzw.U2, wennU1ähnlich zuU2ist, oderU1eine Basisklasse vonU2ist, dann istT1*referenzbezogen* zuT2. - Wenn ein prvalue vom Typ "Zeiger auf
T2" in den Typ "Zeiger aufT1" über eine Standardkonvertierungssequenz konvertiert werden kann, dann istT1*referenzkompatibel* mitT2.
[bearbeiten] Initialisierungsregeln
|
Wenn eine Referenzinitialisierung eine gewöhnliche oder benannte(seit C++20) Listen-Initialisierung verwendet, werden die Regeln der Listen-Initialisierung befolgt. |
(seit C++11) |
Für Nicht-Listen-Referenzinitialisierung, gegeben den Typ von target als U, bindet sich die Referenz entweder *direkt* an target oder an einen Wert vom Typ T, der von target konvertiert wurde. Direkte Bindung wird zuerst betrachtet, gefolgt von indirekter Bindung. Wenn keine Bindung verfügbar ist, ist das Programm ill-formed.
In allen Fällen, in denen die Referenzkompatibilitätsbeziehung zweier Typen verwendet wird, um die Gültigkeit einer Referenzbindung herzustellen, und die Standardkonvertierungssequenz ill-formed wäre, ist ein Programm, das eine solche Bindung erfordert, ill-formed.
[bearbeiten] Direkte Bindung
Wenn alle folgenden Bedingungen erfüllt sind
- Die zu initialisierende Referenz ist eine Lwert-Referenz.
- target ist ein Nicht-Bitfeld-Lwert.
-
Tist referenzkompatibel mitU.
Dann bindet sich die Referenz an target oder an dessen entsprechenden Basisklassen-Subobjekt.
double d = 2.0; double& rd = d; // rd refers to d const double& rcd = d; // rcd refers to d struct A {}; struct B : A {} b; A& ra = b; // ra refers to A subobject in b const A& rca = b; // rca refers to A subobject in b
Andernfalls, wenn alle folgenden Bedingungen erfüllt sind
- Die zu initialisierende Referenz ist eine Lwert-Referenz.
-
Uist ein Klassentyp. -
Tist nicht referenzbezogen zuU. - target kann in einen Lwert vom Typ
Vkonvertiert werden, so dassTreferenzkompatibel mitVist.
Dann bindet sich die Referenz an den Lwert-Ergebnis der Konvertierung oder an dessen entsprechenden Basisklassen-Subobjekt.
struct A {}; struct B : A { operator int&(); }; int& ir = B(); // ir refers to the result of B::operator int&
Andernfalls, wenn die zu initialisierende Referenz eine Lwert-Referenz ist und T nicht const-qualifiziert oder volatile-qualifiziert ist, ist das Programm ill-formed.
double& rd2 = 2.0; // error: not an lvalue and reference is not const int i = 2; double& rd3 = i; // error: type mismatch and reference is not const
Andernfalls, wenn alle folgenden Bedingungen erfüllt sind
- target ist ein Wert einer der folgenden Kategorien
|
(bis C++11) |
|
(seit C++11) (bis C++17) |
|
(seit C++17) |
-
Tist referenzkompatibel mitU.
Dann bindet sich die Referenz an target oder an dessen entsprechenden Basisklassen-Subobjekt.
struct A {}; struct B : A {}; extern B f(); const A& rca2 = f(); // bound to the A subobject of the B rvalue. A&& rra = f(); // same as above int i2 = 42; int&& rri = static_cast<int&&>(i2); // bound directly to i2
|
Wenn target ein prvalue ist, wird Temporär-Materialisierung darauf angewendet, wobei der Typ des prvalue als der angepasste Typ
In diesem Fall bindet sich die Referenz an das Ergebnisobjekt oder an dessen entsprechenden Basisklassen-Subobjekt. |
(seit C++17) |
Andernfalls, wenn alle folgenden Bedingungen erfüllt sind
-
Uist ein Klassentyp. -
Tist nicht referenzbezogen zuU. - target kann in einen Wert v vom Typ
Vkonvertiert werden, so dassTreferenzkompatibel mitVist, wobei v eine der folgenden Kategorien hat
|
(bis C++11) |
|
(seit C++11) (bis C++17) |
|
(seit C++17) |
Dann bindet sich die Referenz an das Ergebnis der Konvertierung oder an dessen entsprechenden Basisklassen-Subobjekt.
struct A {}; struct B : A {}; struct X { operator B(); } x; const A& r = x; // bound to the A subobject of the result of the conversion B&& rrb = x; // bound directly to the result of the conversion
|
Wenn das Ergebnis der Konvertierung ein prvalue ist, wird Temporär-Materialisierung darauf angewendet, wobei der Typ des prvalue als der angepasste Typ
In diesem Fall bindet sich die Referenz an das Ergebnisobjekt oder an dessen entsprechenden Basisklassen-Subobjekt. |
(seit C++17) |
[bearbeiten] Indirekte Bindung
Wenn direkte Bindung nicht verfügbar ist, wird indirekte Bindung betrachtet. In diesem Fall ist T nicht referenzbezogen zu U.
Wenn T oder U ein Klassentyp ist, werden benutzerdefinierte Konvertierungen unter Verwendung der Regeln für die Kopierinitialisierung eines Objekts vom Typ T durch benutzerdefinierte Konvertierung berücksichtigt. Das Programm ist ill-formed, wenn die entsprechende Nicht-Referenz-Kopierinitialisierung ill-formed wäre. Das Ergebnis des Aufrufs der Konvertierungsfunktion, wie für die Nicht-Referenz Kopierinitialisierung beschrieben, wird dann zur direkten Initialisierung der Referenz verwendet. Für diese direkte Initialisierung werden keine benutzerdefinierten Konvertierungen berücksichtigt.
|
Andernfalls wird ein Temporär objekt vom Typ |
(bis C++17) |
|
Andernfalls wird target implizit in einen prvalue vom Typ "cv-unqualifiziertes |
(seit C++17) |
const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 int i3 = 2; double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0
[bearbeiten] Lebensdauer eines Temporär objekts
Immer wenn eine Referenz an ein Temporär objekt oder ein Subobjekt davon gebunden wird, wird die Lebensdauer des Temporär objekts so verlängert, dass sie mit der Lebensdauer der Referenz übereinstimmt (siehe Ausnahmen für die Lebensdauer von Temporär objekten), wobei das Temporär objekt oder sein Subobjekt durch einen der folgenden Ausdrücke bezeichnet wird
|
(bis C++17) |
| (seit C++17) |
- ein in Klammern gesetzter Ausdruck (e), wobei e einer dieser Ausdrücke ist,
- ein eingebauter Subskript-Ausdruck der Form a[n] oder n[a], wobei a ein Array ist und einer dieser Ausdrücke ist,
- ein Klassen-Member-Zugriffs-Ausdruck der Form e.m, wobei e einer dieser Ausdrücke ist und m ein Nicht-Statische-Datenmember eines Objekttyps bezeichnet,
- eine Zeiger-auf-Member-Operation der Form e.*mp, wobei e einer dieser Ausdrücke ist und mp ein Zeiger auf ein Datenmember ist,
- eine
const_cast,static_cast,dynamic_castoderreinterpret_cast-Konvertierung ohne benutzerdefinierte Konvertierung, die einen dieser Ausdrücke in einen Lwert umwandelt, der auf das vom Operanden bezeichnete Objekt oder dessen vollständiges Objekt oder ein Subobjekt davon verweist (ein expliziter Cast-Ausdruck wird als Sequenz dieser Casts interpretiert), - ein bedingter Ausdruck der Form cond ? e1 : e2, der ein Lwert ist, wobei e1 oder e2 einer dieser Ausdrücke ist, oder
- ein eingebauter Komma-Ausdruck der Form x, e, der ein Lwert ist, wobei e einer dieser Ausdrücke ist.
Es gibt folgende Ausnahmen zu dieser Lebensdauerregel
|
(bis C++26) |
- ein Temporär objekt, das an einen Referenzparameter in einem Funktionsaufruf gebunden ist, existiert bis zum Ende des vollständigen Ausdrucks, der diesen Funktionsaufruf enthält: wenn die Funktion eine Referenz zurückgibt, die länger lebt als der vollständige Ausdruck, wird sie zu einer hängenden Referenz.
|
(seit C++11) |
struct A { int&& r; }; A a1{7}; // OK, lifetime is extended A a2(7); // well-formed, but dangling reference |
(seit C++20) |
Im Allgemeinen kann die Lebensdauer eines Temporär objekts nicht weiter verlängert werden, indem es "weitergegeben" wird: eine zweite Referenz, die von der Referenzvariable oder dem Datenmember, an den das Temporär objekt gebunden war, initialisiert wird, beeinflusst dessen Lebensdauer nicht.
[bearbeiten] Anmerkungen
Referenzen ohne Initialisierer treten nur in der Deklaration von Funktionsparametern, in der Deklaration von Funktionsrückgabetypen, in der Deklaration eines Klassenmembers und mit dem extern-Speziplikator auf.
Bis zur Auflösung von CWG Issue 1696 war es gestattet, ein Temporär objekt an ein Referenzmember in der Initialisierungsliste eines Konstruktors zu binden, und es existierte nur bis zum Ende des Konstruktors, nicht so lange wie das Objekt existiert. Eine solche Initialisierung ist seit CWG 1696 ill-formed, obwohl viele Compiler sie immer noch unterstützen (eine bemerkenswerte Ausnahme ist clang).
[bearbeiten] Beispiel
#include <sstream> #include <utility> struct S { int mi; const std::pair<int, int>& mp; // reference member }; void foo(int) {} struct A {}; struct B : A { int n; operator int&() { return n; } }; B bar() { return B(); } //int& bad_r; // error: no initializer extern int& ext_r; // OK int main() { // Lvalues int n = 1; int& r1 = n; // lvalue reference to the object n const int& cr(n); // reference can be more cv-qualified volatile int& cv{n}; // any initializer syntax can be used int& r2 = r1; // another lvalue reference to the object n // int& bad = cr; // error: less cv-qualified int& r3 = const_cast<int&>(cr); // const_cast is needed void (&rf)(int) = foo; // lvalue reference to function int ar[3]; int (&ra)[3] = ar; // lvalue reference to array B b; A& base_ref = b; // reference to base subobject int& converted_ref = b; // reference to the result of a conversion // Rvalues // int& bad = 1; // error: cannot bind lvalue ref to rvalue const int& cref = 1; // bound to rvalue int&& rref = 1; // bound to rvalue const A& cref2 = bar(); // reference to A subobject of B temporary A&& rref2 = bar(); // same int&& xref = static_cast<int&&>(n); // bind directly to n // int&& copy_ref = n; // error: can't bind to an lvalue double&& copy_ref = n; // bind to an rvalue temporary with value 1.0 // Restrictions on temporary lifetimes // std::ostream& buf_ref = std::ostringstream() << 'a'; // the ostringstream temporary was bound to the left operand // of operator<< but its lifetime ended at the semicolon so // the buf_ref is a dangling reference S a {1, {2, 3}}; // temporary pair {2, 3} bound to the reference member // a.mp and its lifetime is extended to match // the lifetime of object a S* p = new S{1, {2, 3}}; // temporary pair {2, 3} bound to the reference // member p->mp, but its lifetime ended at the semicolon // p->mp is a dangling reference delete p; // Imitate [[maybe_unused]] applied to the following variables: [](...){} ( cv, r2, r3, rf, ra, base_ref, converted_ref, a, cref, rref, cref2, rref2, copy_ref, xref ); }
[bearbeiten] Defect reports
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 391 | C++98 | Initialisierung einer Referenz auf einen const-qualifizierten Typ mit einem Klassentyp rvalue kann ein Temporär objekt erzeugen und ein Konstruktor dieser Klasse war erforderlich, um den rvalue in dieses Temporär objekt zu kopieren |
kein Temporär objekt wird erzeugt, Konstruktor wird nicht benötigt |
| CWG 450 | C++98 | Eine Referenz auf ein const-qualifiziertes Array konnte nicht mit einem referenzkompatiblen Array-rvalue initialisiert werden |
erlaubt |
| CWG 589 | C++98 | Eine Referenz konnte sich nicht direkt an ein Array- oder Klassen-rvalue binden | erlaubt |
| CWG 656 | C++98 | Eine Referenz auf einen const-qualifizierten Typ, initialisiert mit einem Typ, der nicht referenzkompatibel ist, aber eine Konvertierungsfunktion zu einem referenzkompatiblen Typ hat, wurde an ein Temporär objekt gebunden, das vom Rückgabewert (oder dessen Basisklassen-Subobjekt) der Konvertierungsfunktion kopiert wurde |
wurde an den Rückgabe- wert (oder dessen Basisklassen- Subobjekt) direkt gebunden |
| CWG 1287 | C++11 | Die Konvertierung von target vom Klassentyp in einen anderen referenzkompatiblen Typ konnte nur implizit erfolgen |
erlaube explizite Konvertierungen |
| CWG 1295 | C++11 | Eine Referenz konnte sich an ein Bitfeld-xvalue binden | verboten |
| CWG 1299 | C++98 | Die Definition von Temporär objekt war unklar | wurde klargestellt |
| CWG 1571 | C++98 | Benutzerdefinierte Konvertierungen bei indirekter Bindung berücksichtigten nicht den Typ von target |
berücksichtigt. |
| CWG 1604 | C++98 | Benutzerdefinierte Konvertierungen wurden bei indirekter Bindung nicht berücksichtigt | berücksichtigt. |
| CWG 2352 | C++98 | Referenzkompatibilität berücksichtigte keine Qualifikationskonvertierungen | berücksichtigt. |
| CWG 2481 | C++17 | Die cv-Qualifikation wurde nicht zum Ergebnistyp der Temporär-Materialisierung bei indirekter Bindung hinzugefügt |
hinzugefügt |
| CWG 2657 | C++17 | Die cv-Qualifikation wurde nicht zum Ergebnistyp der Temporär-Materialisierung bei direkter Bindung |
hinzugefügt |
| CWG 2801 | C++98 | Referenzbezogene Typen waren für die indirekte Bindung zugelassen | verboten |