Reference initialization
Bindet eine Referenz an ein Objekt.
Inhaltsverzeichnis |
Syntax
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 ) | |||||||
Gewöhnliche Listeninitialisierung (seit C++11)
T
&
ref
= {
arg1
,
arg2
,
...
};
T
|
(1) | ||||||||
T
&&
ref
= {
arg1
,
arg2
,
...
};
T
|
(2) | ||||||||
func-refpar
({
arg1
,
arg2
,
...
});
|
(3) | ||||||||
Designierte Listeninitialisierung (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 gesetzt (geändert) werden, um auf ein anderes Objekt zu verweisen.
Referenzen werden in den folgenden Situationen initialisiert:
Erklärung
| T | - | der referenzierte Typ |
| ref | - | die zu initialisierende Referenzvariable |
| target | - | der verwendete Initialisierungsausdruck |
| 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)
)
|
| Class | - | ein Klassenname |
| ref-member | - |
ein nicht-statisches Datenelement vom Referenztyp (
T
&
oder
T
&&
(seit C++11)
) von
Class
|
| des1 , des2 , ... | - | Designatoren |
| arg1 , arg2 , ... | - | die Initialisierer in Initialisierungslisten |
Definitionen
Für zwei Typen
T1
und
T2
:
-
Gegeben die cv-unqualifizierten Versionen von
T1undT2alsU1bzw.U2, wennU1ähnlich zuU2ist, oderU1eine Basisklasse vonU2ist, dann istT1referenzbezogen zuT2. -
Wenn ein Prvalue vom Typ "Zeiger auf
T2" durch eine Standardkonvertierungssequenz zum Typ "Zeiger aufT1" konvertiert werden kann, dann istT1referenzkompatibel mitT2.
Initialisierungsregeln
|
Wenn eine Referenzinitialisierung eine gewöhnliche oder designierte (seit C++20) Listeninitialisierung verwendet, werden die Regeln der Listeninitialisierung befolgt. |
(seit C++11) |
Für die Nicht-Listen-Referenzinitialisierung, gegeben den Typ von
target
als
U
, bindet die Referenz entweder
direkt
an
target
oder bindet an einen Wert vom Typ
T
, der von
target
konvertiert wurde. Direktes Binden wird zuerst betrachtet, gefolgt von indirektem Binden; falls keine der Bindungen verfügbar ist, ist das Programm fehlerhaft.
In allen Fällen, in denen die referenzkompatible Beziehung zweier Typen verwendet wird, um die Gültigkeit einer Referenzbindung festzustellen, und die Standardkonvertierungssequenz fehlerhaft wäre, ist ein Programm, das eine solche Bindung erfordert, fehlerhaft.
Direkte Bindung
Wenn alle folgenden Bedingungen erfüllt sind:
- Die zu initialisierende Referenz ist eine Lvalue-Referenz.
- target ist ein Nicht- Bitfeld -Lvalue.
-
Tist referenzkompatibel mitU.
Dann bindet die Referenz an target , oder an deren entsprechendes Basisklassen-Subobjekt:
double d = 2.0; double& rd = d; // rd referenziert d const double& rcd = d; // rcd referenziert d struct A {}; struct B : A {} b; A& ra = b; // ra referenziert A-Subobjekt in b const A& rca = b; // rca referenziert A-Subobjekt in b
Andernfalls, wenn alle folgenden Bedingungen erfüllt sind:
- Die zu initialisierende Referenz ist eine Lvalue-Referenz.
-
Uist ein Klassentyp. -
Tist nicht referenzbezogen zuU. -
target
kann in einen Lvalue des Typs
Vkonvertiert werden, sodassTreferenzkompatibel mitVist.
Dann bindet die Referenz an das Lvalue-Ergebnis der Konvertierung oder an dessen entsprechendes Basisklassen-Subobjekt:
struct A {}; struct B : A { operator int&(); }; int& ir = B(); // ir referenziert das Ergebnis von B::operator int&
Andernfalls, wenn die zu initialisierende Referenz eine Lvalue-Referenz ist und
T
nicht const-qualifiziert oder volatile-qualifiziert ist, ist das Programm fehlerhaft:
double& rd2 = 2.0; // Fehler: kein L-Wert und Referenz ist nicht const int i = 2; double& rd3 = i; // Fehler: Typkonflikt und Referenz ist nicht 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 die Referenz an target , oder an deren entsprechendes Basisklassen-Subobjekt:
struct A {}; struct B : A {}; extern B f(); const A& rca2 = f(); // gebunden an das A-Subobjekt des B-Rvalues. A&& rra = f(); // dasselbe wie oben int i2 = 42; int&& rri = static_cast<int&&>(i2); // direkt an i2 gebunden
|
Wenn
target
ein Prvalue ist,
wird darauf
temporäre Materialisierung
angewendet, wobei der Typ des Prvalue als der angepasste Typ
In diesem Fall bindet die Referenz an das Ergebnisobjekt oder an dessen entsprechende Basisklassen-Subobjekt. |
(since 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, sodassTreferenzkompatibel mitVist, wobei v einer der folgenden Kategorien entspricht:
|
(bis C++11) |
|
(seit C++11)
(bis C++17) |
|
(seit C++17) |
Dann bindet die Referenz an das Ergebnis der Konvertierung oder an ihr entsprechendes Basisklassen-Subobjekt:
struct A {}; struct B : A {}; struct X { operator B(); } x; const A& r = x; // gebunden an das A-Subobjekt des Ergebnisses der Konvertierung B&& rrb = x; // direkt an das Ergebnis der Konvertierung gebunden
|
Wenn das Ergebnis der Konvertierung ein Prvalue ist,
Temporary Materialization
wird darauf angewendet, wobei der Typ des Prvalue als der angepasste Typ
In diesem Fall bindet die Referenz an das Ergebnisobjekt oder an dessen entsprechende Basisklassen-Subobjekt. |
(seit C++17) |
Indirekte Bindung
Wenn keine direkte Bindung verfügbar ist, wird eine indirekte Bindung in Betracht gezogen. In diesem Fall kann
T
nicht referenzbezogen zu
U
sein.
Wenn
T
oder
U
ein Klassentyp ist, werden benutzerdefinierte Konvertierungen gemäß den Regeln für die
Kopierinitialisierung
eines Objekts vom Typ
T
durch benutzerdefinierte Konvertierung berücksichtigt. Das Programm ist fehlerhaft, wenn die entsprechende Nicht-Referenz-Kopierinitialisierung fehlerhaft wäre. Das Ergebnis des Aufrufs der Konvertierungsfunktion, wie für die Nicht-Referenz-
Kopierinitialisierung
beschrieben, wird dann zur Direktinitialisierung der Referenz verwendet. Für diese Direktinitialisierung werden benutzerdefinierte Konvertierungen nicht berücksichtigt.
|
Andernfalls wird ein Temporär vom Typ
|
(until C++17) |
|
Andernfalls wird
target
implizit in einen Prvalue des Typs "cv-unqualified
|
(since C++17) |
const std::string& rs = "abc"; // rs referenziert eine temporäre Kopie, die vom char-Array kopierinitialisiert wurde const double& rcd2 = 2; // rcd2 referenziert einen temporären Wert mit dem Wert 2.0 int i3 = 2; double&& rrd3 = i3; // rrd3 referenziert einen temporären Wert mit dem Wert 2.0
Lebensdauer eines Temporären
Immer wenn eine Referenz an ein temporäres Objekt oder an ein Unterobjekt davon gebunden wird, wird die Lebensdauer des temporären Objekts erweitert, um der Lebensdauer der Referenz zu entsprechen (siehe Ausnahmen bei der Lebensdauer temporärer Objekte ), wobei das temporäre Objekt oder sein Unterobjekt durch einen der folgenden Ausdrücke bezeichnet wird:
|
(bis C++17) |
| (seit C++17) |
- ein geklammerter Ausdruck ( e ) , wobei e einer dieser Ausdrücke ist,
- ein eingebauter Indexausdruck der Form a [ n ] oder n [ a ] , wobei a ein Array ist und einer dieser Ausdrücke ist,
- ein Klassen-Member-Zugriffsausdruck der Form e. m , wobei e einer dieser Ausdrücke ist und m einen nicht-statischen Datenelement-Member vom Objekttyp bezeichnet,
- eine Zeiger-auf-Member-Operation der Form e. * mp , wobei e einer dieser Ausdrücke ist und mp ein Zeiger auf einen Datenelement-Member ist,
-
eine
const_cast-,static_cast-,dynamic_cast- oderreinterpret_cast-Konvertierung ohne benutzerdefinierte Konvertierung, die einen dieser Ausdrücke in den L-Wert konvertiert, der auf das durch den Operanden bezeichnete Objekt oder dessen vollständiges Objekt oder ein Unterobjekt davon verweist (ein expliziter Cast-Ausdruck wird als Sequenz dieser Casts interpretiert), - ein bedingter Ausdruck der Form cond ? e1 : e2 , der ein L-Wert ist, wobei e1 oder e2 einer dieser Ausdrücke ist, oder
- ein eingebauter Komma-Ausdruck der Form x, e , der ein L-Wert ist, wobei e einer dieser Ausdrücke ist.
Es gelten folgende Ausnahmen von dieser Lebensdauerregel:
|
(bis C++26) |
- Ein temporäres 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 über den vollständigen Ausdruck hinaus bestehen bleibt, wird sie zu einer hängenden Referenz.
|
(since C++11) |
struct A { int&& r; }; A a1{7}; // OK, Lebensdauer wird erweitert A a2(7); // wohlgeformt, aber hängende Referenz |
(seit C++20) |
Im Allgemeinen kann die Lebensdauer eines temporären Objekts nicht weiter verlängert werden, indem "es weitergegeben wird": Ein zweiter Verweis, der von der Referenzvariablen oder dem Datenelement initialisiert wird, an das das temporäre Objekt gebunden war, beeinflusst dessen Lebensdauer nicht.
Hinweise
Referenzen erscheinen ohne Initialisierer nur in Funktionsparameterdeklarationen, in Funktionsrückgabetypdeklarationen, in der Deklaration eines Klassenmembers und mit dem
extern
Spezifizierer.
Bis zur Lösung von CWG issue 1696 war es erlaubt, dass ein temporäres Objekt an ein Referenzmitglied in einer Initialisiererliste eines Konstruktors gebunden wird, und es bestand nur bis zum Verlassen des Konstruktors, nicht so lange wie das Objekt existiert. Eine solche Initialisierung ist seit CWG 1696 fehlerhaft, obwohl viele Compiler sie weiterhin unterstützen (eine bemerkenswerte Ausnahme ist clang).
Beispiel
#include <sstream> #include <utility> struct S { int mi; const std::pair<int, int>& mp; // Referenz-Member }; void foo(int) {} struct A {}; struct B : A { int n; operator int&() { return n; } }; B bar() { return B(); } //int& bad_r; // Fehler: kein Initialisierer extern int& ext_r; // OK int main() { // Lvalues int n = 1; int& r1 = n; // Lvalue-Referenz auf das Objekt n const int& cr(n); // Referenz kann mehr cv-qualifiziert sein volatile int& cv{n}; // Jede Initialisierungssyntax kann verwendet werden int& r2 = r1; // Weitere Lvalue-Referenz auf das Objekt n // int& bad = cr; // Fehler: weniger cv-qualifiziert int& r3 = const_cast<int&>(cr); // const_cast ist erforderlich void (&rf)(int) = foo; // Lvalue-Referenz auf Funktion int ar[3]; int (&ra)[3] = ar; // Lvalue-Referenz auf Array B b; A& base_ref = b; // Referenz auf Basis-Subobjekt int& converted_ref = b; // Referenz auf das Ergebnis einer Konvertierung // Rvalues // int& bad = 1; // Fehler: Lvalue-Ref kann nicht an Rvalue gebunden werden const int& cref = 1; // an Rvalue gebunden int&& rref = 1; // an Rvalue gebunden const A& cref2 = bar(); // Referenz auf A-Subobjekt von B-Temporär A&& rref2 = bar(); // dasselbe int&& xref = static_cast<int&&>(n); // direkt an n binden // int&& copy_ref = n; // Fehler: kann nicht an Lvalue gebunden werden double&& copy_ref = n; // an Rvalue-Temporär mit Wert 1.0 binden // Einschränkungen bei Temporär-Lebensdauern // std::ostream& buf_ref = std::ostringstream() << 'a'; // das ostringstream-Temporär wurde an den linken Operanden // von operator<< gebunden, aber seine Lebensdauer endete beim Semikolon, // daher ist buf_ref eine hängende Referenz S a {1, {2, 3}}; // Temporäres Paar {2, 3} an das Referenz-Member // a.mp gebunden und seine Lebensdauer wird erweitert, um // der Lebensdauer von Objekt a zu entsprechen S* p = new S{1, {2, 3}}; // Temporäres Paar {2, 3} an das Referenz- // Member p->mp gebunden, aber seine Lebensdauer endete beim Semikolon // p->mp ist eine hängende Referenz delete p; // Imitiere [[maybe_unused]] angewendet auf folgende Variablen: [](...){} ( cv, r2, r3, rf, ra, base_ref, converted_ref, a, cref, rref, cref2, rref2, copy_ref, xref ); }
Fehlerberichte
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 const-qualifizierten Typ mit einem Klassen-Rvalue
könnte ein Temporär erzeugen, und ein Konstruktor dieser Klasse war erforderlich, um den Rvalue in dieses Temporär zu kopieren |
Es wird kein Temporär erzeugt,
Konstruktor ist nicht erforderlich |
| CWG 450 | C++98 |
Eine Referenz auf const-qualifiziertes Array konnte nicht
mit einem referenzkompatiblen Array-Rvalue initialisiert werden |
Erlaubt |
| CWG 589 | C++98 | Eine Referenz konnte nicht direkt an einen Array- oder Klassen-Rvalue binden | Erlaubt |
| CWG 656 | C++98 |
Eine Referenz auf const-qualifizierten Typ, initialisiert mit einem nicht
referenzkompatiblen Typ, der aber eine Konvertierungsfunktion zu einem referenz- kompatiblen Typ besitzt, wurde an ein Temporär gebunden, das vom Rückgabe- wert (oder seinem Basisklassen-Subobjekt) der Konvertierungsfunktion kopiert wurde |
Direkt an den Rückgabewert
(oder sein Basisklassen- Subobjekt) gebunden |
| CWG 1287 | C++11 |
Die Konvertierung vom
target
vom Klassentyp zu einem anderen
referenzkompatiblen Typ konnte nur implizit erfolgen |
Explizite Konvertierungen
erlaubt |
| CWG 1295 | C++11 | Eine Referenz konnte an ein Bitfeld-XValue binden | Verboten |
| CWG 1299 | C++98 | Die Definition von Temporär war unklar | 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 |
CV-Qualifikation wurde nicht zum Ergebnistyp
der Temporärmaterialisierung bei indirekter Bindung hinzugefügt |
Hinzugefügt |
| CWG 2657 | C++17 |
CV-Qualifikation wurde nicht zum Ergebnistyp
der Temporärmaterialisierung bei direkter Bindung hinzugefügt |
Hinzugefügt |
| CWG 2801 | C++98 | Referenz-verwandte Typen waren für indirekte Bindung erlaubt | Verboten |