Member-Zugriffsoperatoren
Greift auf ein Element seines Operanden zu.
| Operator Name | Syntax | Überladbar | Prototypenbeispiele (für class T) | |
|---|---|---|---|---|
| Innerhalb der Klassendefinition | Außerhalb der Klassendefinition | |||
| Indexzugriff | a[b] | Ja | R& T::operator[](S b); | N/A |
| a[...] (seit C++23) | R& T::operator[](...); | |||
| Indirektion | *a | Ja | R& T::operator*(); | R& operator*(T a); |
| Adressoperator | &a | Ja | R* T::operator&(); | R* operator&(T a); |
| Element eines Objekts | a.b | Nein | N/A | N/A |
| Element eines Zeigers | a->b | Ja | R* T::operator->(); | N/A |
| Zeiger auf Element eines Objekts | a.*b | Nein | N/A | N/A |
| Zeiger auf Element eines Zeigers | a->*b | Ja | R& T::operator->*(S b); | R& operator->*(T a, S b); |
| ||||
Inhalt |
[bearbeiten] Erklärung
Der integrierte Indexoperator ermöglicht den Zugriff auf ein Objekt, auf das der Zeiger- oder Array-Operand zeigt.
Der integrierte Indirektionsoperator ermöglicht den Zugriff auf ein Objekt oder eine Funktion, auf die der Zeigeroperand zeigt.
Der integrierte Adressoperator erstellt einen Zeiger, der auf das Objekt oder die Funktion des Operanden zeigt.
Die Operatoren "Element eines Objekts" und "Zeiger auf Element eines Objekts" ermöglichen den Zugriff auf ein Datenelement oder eine Memberfunktion des Objektoperanden.
Die integrierten Operatoren "Element eines Zeigers" und "Zeiger auf Element eines Zeigers" ermöglichen den Zugriff auf ein Datenelement oder eine Memberfunktion der Klasse, auf die der Zeigeroperand zeigt.
[bearbeiten] Integrierter Indexoperator
Indexoperator-Ausdrücke haben die Form
expr1 [expr2 ] |
(1) | ||||||||
expr1 [{expr , ...}] |
(2) | (seit C++11) | |||||||
expr1 [expr2 , expr , ...] |
(3) | (seit C++23) | |||||||
T" oder ein Prvalue vom Typ "Zeiger auf T" sein, während der andere Ausdruck (expr2 bzw. expr1) ein Prvalue einer unaufgelösten Enumeration oder eines ganzzahligen Typs sein muss. Das Ergebnis dieses Ausdrucks hat den Typ T. expr2 darf kein unparenthetischer Komma-Ausdruck sein.(seit C++23)Der integrierte Indexausdruck E1[E2] ist exakt identisch mit dem Ausdruck *(E1 + E2), abgesehen von seiner Wertkategorie (siehe unten) und Auswertungsreihenfolge(seit C++17): Der Zeigeroperand (der das Ergebnis einer Array-zu-Zeiger-Konvertierung sein kann und auf ein Element eines Arrays oder ein Element danach zeigen muss) wird so angepasst, dass er auf ein anderes Element desselben Arrays zeigt, gemäß den Regeln der Zeigerarithmetik, und wird dann dereferenziert.
Wenn auf ein Array angewendet, ist der Indexausdruck ein lvalue, wenn das Array ein lvalue ist, und ein xvalue, wenn nicht(seit C++11).
Wenn auf einen Zeiger angewendet, ist der Indexausdruck immer ein lvalue.
Der Typ T darf kein unvollständiger Typ sein, auch wenn die Größe oder interne Struktur von T nie verwendet wird, wie in &x[0].
|
Die Verwendung eines unparenthetischen Komma-Ausdrucks als zweites (rechtes) Argument eines Indexoperators ist veraltet. Zum Beispiel ist a[b, c] veraltet und a[(b, c)] nicht. |
(seit C++20) (bis C++23) |
|
Ein unparenthetischer Komma-Ausdruck darf nicht das zweite (rechte) Argument eines Indexoperators sein. Zum Beispiel ist a[b, c] entweder fehlerhaft oder äquivalent zu a.operator[](b, c). Klammern sind erforderlich, um einen Komma-Ausdruck als Index zu verwenden, z. B. a[(b, c)]. |
(seit C++23) |
Bei der Überladungsauflösung für benutzerdefinierte Operatoren nimmt für jedes Objekttyp T (möglicherweise cv-qualifiziert) die folgende Funktionssignatur an der Überladungsauflösung teil:
| T& operator[](T*, std::ptrdiff_t); |
||
| T& operator[](std::ptrdiff_t, T*); |
||
#include <iostream> #include <map> #include <string> int main() { int a[4] = {1, 2, 3, 4}; int* p = &a[2]; std::cout << p[1] << p[-1] << 1[p] << (-1)[p] << '\n'; std::map<std::pair<int, int>, std::string> m; m[{1, 2}] = "abc"; // uses the [{...}] version }
Ausgabe
4242
[bearbeiten] Integrierter Indirektionsoperator
Indirektionsoperator-Ausdrücke haben die Form
*expr |
|||||||||
Der Operand des integrierten Indirektionsoperators muss ein Zeiger auf ein Objekt oder eine Zeiger auf eine Funktion sein, und das Ergebnis ist das lvalue, das sich auf das Objekt oder die Funktion bezieht, auf die expr zeigt. Wenn expr nicht tatsächlich auf ein Objekt oder eine Funktion zeigt, ist das Verhalten undefiniert (mit Ausnahme des von typeid spezifizierten Falls).
Ein Zeiger auf void (möglicherweise cv-qualifiziert) kann nicht dereferenziert werden. Zeiger auf andere unvollständige Typen können dereferenziert werden, aber das resultierende lvalue darf nur in Kontexten verwendet werden, die ein lvalue eines unvollständigen Typs zulassen, z. B. bei der Initialisierung einer Referenz.
Bei der Überladungsauflösung für benutzerdefinierte Operatoren nimmt für jeden Typ T, der entweder ein Objekttyp (möglicherweise cv-qualifiziert) oder ein Funktionstyp (nicht const- oder ref-qualifiziert) ist, die folgende Funktionssignatur an der Überladungsauflösung teil:
| T& operator*(T*); |
||
#include <iostream> int f() { return 42; } int main() { int n = 1; int* pn = &n; int& r = *pn; // lvalue can be bound to a reference int m = *pn; // indirection + lvalue-to-rvalue conversion int (*fp)() = &f; int (&fr)() = *fp; // function lvalue can be bound to a reference [](...){}(r, m, fr); // removes possible "unused variable" warnings }
[bearbeiten] Integrierter Adressoperator
Adressoperator-Ausdrücke haben die Form
&expr |
(1) | ||||||||
&Klasse ::Mitglied |
(2) | ||||||||
T ist, erstellt und gibt operator& einen prvalue vom Typ T* mit derselben cv-Qualifikation zurück, der auf das Objekt oder die Funktion zeigt, auf die der Operand verweist. Wenn der Operand einen unvollständigen Typ hat, kann der Zeiger gebildet werden, aber wenn dieser unvollständige Typ eine Klasse ist, die ihren eigenen operator& definiert, ist es nicht spezifiziert, ob der integrierte oder der überladene Operator verwendet wird. Für Operanden vom Typ mit benutzerdefiniertem operator& kann std::addressof verwendet werden, um den tatsächlichen Zeiger zu erhalten. Beachten Sie, dass im Gegensatz zu C99 und späteren C-Versionen keine Sonderbehandlung für den unären operator& angewendet wird, der auf das Ergebnis des unären operator* angewendet wird.|
Wenn expr eine explizite Objekt-Memberfunktion benennt, muss expr ein qualifizierter Bezeichner sein. Das Anwenden von |
(seit C++23) |
T in Klasse C. Beachten Sie, dass weder &member noch C::member oder sogar &(C::member) zur Initialisierung eines Zeigers auf ein Mitglied verwendet werden können.Bei der Überladungsauflösung für benutzerdefinierte Operatoren führt dieser Operator keine zusätzlichen Funktionssignaturen ein: Der integrierte Adressoperator wird nicht angewendet, wenn ein überladener operator& existiert, der eine funktionierende Funktion ist.
void f(int) {} void f(double) {} struct A { int i; }; struct B { void f(); }; int main() { int n = 1; int* pn = &n; // pointer int* pn2 = &*pn; // pn2 == pn int A::* mp = &A::i; // pointer to data member void (B::*mpf)() = &B::f; // pointer to member function void (*pf)(int) = &f; // overload resolution due to initialization context // auto pf2 = &f; // error: ambiguous overloaded function type auto pf2 = static_cast<void (*)(int)>(&f); // overload resolution due to cast }
[bearbeiten] Integrierte Member-Zugriffsoperatoren
Member-Zugriffsoperator-Ausdrücke haben die Form
expr .template(optional) id-expr |
(1) | ||||||||
expr ->template(optional) id-expr |
(2) | ||||||||
expr .pseudo-destructor |
(3) | ||||||||
expr ->pseudo-destructor |
(4) | ||||||||
T* sein.id-expr ist ein Name für (formal, ein Bezeichnerausdruck, der benennt) ein Datenmember oder eine Memberfunktion von T oder einer eindeutigen und zugänglichen Basisklasse B von T (z. B. E1.E2 oder E1->E2), optional qualifiziert (z. B. E1.B::E2 oder E1->B::E2), optional unter Verwendung des template-Disambiguators (z. B. E1.template E2 oder E1->template E2).
Wenn ein benutzerdefinierter operator-> aufgerufen wird, wird operator-> rekursiv auf dem resultierenden Wert erneut aufgerufen, bis ein operator-> erreicht wird, der einen einfachen Zeiger zurückgibt. Danach werden die integrierten Semantiken auf diesen Zeiger angewendet.
Der Ausdruck E1->E2 ist für integrierte Typen exakt äquivalent zu (*E1).E2; daher befassen sich die folgenden Regeln nur mit E1.E2.
Im Ausdruck E1.E2
- Wenn E2 vom Referenztyp
T&ist oderT&&(seit C++11), ist das Ergebnis ein lvalue vom TypT, das das Objekt oder die Funktion bezeichnet, an die die Referenz gebunden ist. - Andernfalls, wenn der Typ von E2
Tist, ist das Ergebnis ein lvalue vom TypT, das dieses statische Datenelement bezeichnet.
- Wenn E2 vom Referenztyp
T&ist oderT&&(seit C++11), ist das Ergebnis ein lvalue vom TypT, das das Objekt oder die Funktion bezeichnet, an die die entsprechende Referenz von E1 gebunden ist. - Andernfalls, wenn E1 ein lvalue ist, ist das Ergebnis ein lvalue, das dieses nicht-statische Datenelement von E1 bezeichnet.
- Andernfalls (wenn E1 ein rvalue(bis C++17)xvalue ist (was von prvalue materialisiert werden kann)(seit C++17)), ist das Ergebnis ein rvalue(bis C++11)xvalue(seit C++11), das dieses nicht-statische Datenelement von E1 bezeichnet.
- Wenn E2 eine statische Memberfunktion ist, ist das Ergebnis ein lvalue, das diese statische Memberfunktion bezeichnet. Im Wesentlichen wird E1 in diesem Fall ausgewertet und verworfen.
- Andernfalls (wenn E2 eine nicht-statische Memberfunktion ist), ist das Ergebnis ein prvalue, das diese nicht-statische Memberfunktion von E1 bezeichnet.
T ein rvalue(bis C++11)prvalue(seit C++11) dessen Wert der Wert des Enumerators ist.~ gefolgt vom Typnamen oder decltype-Spezifizierer ist, der denselben Typ bezeichnet (abzüglich cv-Qualifikationen), optional qualifiziert, ist das Ergebnis eine spezielle Art von prvalue, die nur als linker Operand eines Funktionsaufrufoperators verwendet werden kann und für keinen anderen Zweck.operator. kann nicht überladen werden, und für operator-> führt bei der Überladungsauflösung für benutzerdefinierte Operatoren der integrierte Operator keine zusätzlichen Funktionssignaturen ein: Der integrierte operator-> wird nicht angewendet, wenn ein überladener operator-> existiert, der eine funktionierende Funktion ist.
#include <cassert> #include <iostream> #include <memory> struct P { template<typename T> static T* ptr() { return new T; } }; template<typename T> struct A { A(int n): n(n) {} int n; static int sn; int f() { return 10 + n; } static int sf() { return 4; } class B {}; enum E {RED = 1, BLUE = 2}; void g() { typedef int U; // keyword template needed for a dependent template member int* p = T().template ptr<U>(); p->~U(); // U is int, calls int's pseudo destructor delete p; } }; template<> int A<P>::sn = 2; struct UPtrWrapper { std::unique_ptr<std::string> uPtr; std::unique_ptr<std::string>& operator->() { return uPtr; } }; int main() { A<P> a(1); std::cout << a.n << ' ' << a.sn << ' ' // A::sn also works << a.f() << ' ' << a.sf() << ' ' // A::sf() also works // << &a.f << ' ' // error: ill-formed if a.f is not the // left-hand operand of operator() // << a.B << ' ' // error: nested type not allowed << a.RED << ' '; // enumerator UPtrWrapper uPtrWrap{std::make_unique<std::string>("wrapped")}; assert(uPtrWrap->data() == uPtrWrap.operator->().operator->()->data()); }
Ausgabe
1 2 11 4 1
Wenn E2 ein nicht-statisches Element ist und das Ergebnis von E1 ein Objekt ist, dessen Typ nicht ähnlich zum Typ von E1 ist, ist das Verhalten undefiniert.
struct A { int i; }; struct B { int j; }; struct D : A, B {}; void f() { D d; static_cast<B&>(d).j; // OK, object expression designates the B subobject of d reinterpret_cast<B&>(d).j; // undefined behavior }
[bearbeiten] Integrierte Zeiger-auf-Member-Zugriffsoperatoren
Member-Zugriffsoperator-Ausdrücke über Zeiger auf Member haben die Form
lhs .*rhs |
(1) | ||||||||
lhs ->*rhs |
(2) | ||||||||
T sein.T* sein.rhs muss ein rvalue vom Typ Zeiger auf Member (Daten oder Funktion) von T oder Zeiger auf Member einer eindeutigen und zugänglichen Basisklasse B von T sein.
Der Ausdruck E1->*E2 ist für integrierte Typen exakt äquivalent zu (*E1).*E2; daher befassen sich die folgenden Regeln nur mit E1.*E2.
Im Ausdruck E1.*E2
- wenn E1 ein lvalue ist, ist das Ergebnis ein lvalue, das dieses Datenelement bezeichnet,
- andernfalls (wenn E1 ein rvalue(bis C++17)xvalue ist (was von prvalue materialisiert werden kann)(seit C++17)), ist das Ergebnis ein rvalue(bis C++11)xvalue(seit C++11), das dieses Datenelement bezeichnet;
& zeigt, ist das Programm fehlerhaft es sei denn, die Memberfunktion hat die cv-Qualifikation const, aber nicht volatile(seit C++20);|
7) Wenn E1 ein lvalue ist und E2 auf eine Memberfunktion mit einem Ref-Qualifier
&& zeigt, ist das Programm fehlerhaft. |
(seit C++11) |
Bei der Überladungsauflösung für benutzerdefinierte Operatoren wird für jede Kombination von Typen D, B, R, wobei der Klassentyp B entweder die gleiche Klasse wie D oder eine eindeutige und zugängliche Basisklasse von D ist und R entweder ein Objekt- oder ein Funktionstyp ist, die folgende Funktionssignatur zur Überladungsauflösung herangezogen:
| R& operator->*(D*, R B::*); |
||
wobei beide Operanden cv-qualifiziert sein können. In diesem Fall ist die cv-Qualifikation des Rückgabetyps die Vereinigung der cv-Qualifikationen der Operanden.
#include <iostream> struct S { S(int n) : mi(n) {} mutable int mi; int f(int n) { return mi + n; } }; struct D : public S { D(int n) : S(n) {} }; int main() { int S::* pmi = &S::mi; int (S::* pf)(int) = &S::f; const S s(7); // s.*pmi = 10; // error: cannot modify through mutable std::cout << s.*pmi << '\n'; D d(7); // base pointers work with derived object D* pd = &d; std::cout << (d.*pf)(7) << ' ' << (pd->*pf)(8) << '\n'; }
Ausgabe
7 14 15
[bearbeiten] Standardbibliothek
Der Indexoperator wird von vielen Standard-Containerklassen überladen.
| greift auf ein bestimmtes Bit zu (öffentliche Memberfunktion von std::bitset<N>) | |
| ermöglicht den indizierten Zugriff auf das verwaltete Array (öffentliche Memberfunktion von std::unique_ptr<T,Deleter>) | |
| greift auf das angegebene Zeichen zu (öffentliche Memberfunktion von std::basic_string<CharT,Traits,Allocator>) | |
| Greift auf ein bestimmtes Element zu (öffentliche Memberfunktion von std::array<T,N>) | |
| Greift auf ein bestimmtes Element zu (öffentliche Memberfunktion von std::deque<T,Allocator>) | |
| Greift auf ein bestimmtes Element zu (öffentliche Memberfunktion von std::vector<T,Allocator>) | |
| greift auf ein Element zu oder fügt es ein (öffentliche Memberfunktion von std::map<Key,T,Compare,Allocator>) | |
| greift auf ein Element zu oder fügt es ein (öffentliche Memberfunktion von std::unordered_map<Key,T,Hash,KeyEqual,Allocator>) | |
| greift per Index auf ein Element zu (öffentliche Memberfunktion von std::reverse_iterator<Iter>) | |
| greift per Index auf ein Element zu (öffentliche Memberfunktion von std::move_iterator<Iter>) | |
| liest/schreibt Array-Element, Slice oder Maske (öffentliche Memberfunktion von std::valarray<T>) | |
| gibt die angegebene Teilübereinstimmung zurück (öffentliche Memberfunktion von std::match_results<BidirIt,Alloc>) |
Die Dereferenzierungs- und Memberoperatoren werden von vielen Iteratoren und Smart-Pointer-Klassen überladen.
| Dereferenziert den Zeiger auf das verwaltete Objekt (öffentliche Memberfunktion von std::unique_ptr<T,Deleter>) | |
| dereferenziert den gespeicherten Zeiger (öffentliche Memberfunktion von std::shared_ptr<T>) | |
| greift auf das verwaltete Objekt zu (öffentliche Memberfunktion von std::auto_ptr<T>) | |
| dereferenziert den Iterator (öffentliche Memberfunktion von std::raw_storage_iterator<OutputIt,T>) | |
| dereferenziert den dekrementierten zugrundeliegenden Iterator (öffentliche Memberfunktion von std::reverse_iterator<Iter>) | |
| no-op (öffentliche Memberfunktion von std::back_insert_iterator<Container>) | |
| no-op (öffentliche Memberfunktion von std::front_insert_iterator<Container>) | |
| no-op (öffentliche Memberfunktion von std::insert_iterator<Container>) | |
| greift auf das dereferenzierte Element zu (öffentliche Memberfunktion von std::move_iterator<Iter>) | |
| gibt das aktuelle Element zurück (öffentliche Memberfunktion von std::istream_iterator<T,CharT,Traits,Distance>) | |
| no-op (öffentliche Memberfunktion von std::ostream_iterator<T,CharT,Traits>) | |
| erhält eine Kopie des aktuellen Zeichens (öffentliche Memberfunktion von std::istreambuf_iterator<CharT,Traits>) | |
| no-op (öffentliche Memberfunktion von std::ostreambuf_iterator<CharT,Traits>) | |
| greift auf die aktuelle Übereinstimmung zu (öffentliche Memberfunktion von std::regex_iterator<BidirIt,CharT,Traits>) | |
| greift auf die aktuelle Teilübereinstimmung zu (öffentliche Memberfunktion von std::regex_token_iterator<BidirIt,CharT,Traits>) |
Keine Standardbibliotheksklassen überladen operator&. Das bekannteste Beispiel für überladenes operator& ist die Microsoft COM-Klasse CComPtr, obwohl es auch in EDSLs wie boost.spirit vorkommen kann.
Keine Standardbibliotheksklassen überladen operator->*. Es wurde vorgeschlagen, dass es Teil des Smart-Pointer-Interfaces sein könnte und in dieser Funktion von Akteuren in boost.phoenix verwendet wird, ist aber in EDSLs wie cpp.react häufiger anzutreffen.
[bearbeiten] Anmerkungen
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_multidimensional_subscript |
202110L |
(C++23) | Mehrdimensionaler Indexoperator |
[bearbeiten] Defektberichte
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 1213 | C++11 | Das Indizieren eines Array-Rvalues ergab ein lvalue | wurde als xvalue klassifiziert |
| CWG 1458 | C++98 | das Anwenden von & auf ein lvalue eines unvollständigen Klassentyps, dasoperator& deklariert, führte zu undefiniertem Verhalten |
es ist nicht spezifiziert welches & verwendet wird |
| CWG 1642 | C++98 | die rhs in eingebauten Zeiger-auf-Member-Zugriffsoperatoren könnte ein lvalue sein | kann nur ein rvalue sein |
| CWG 1800 | C++98 | beim Anwenden von & auf ein nicht-statisches Datenelement einerMember-anonymen-Union, war es unklar, ob die anonyme Union am Ergebnistyp beteiligt ist |
die anonyme Union ist nicht enthalten in dem Ergebnistyp |
| CWG 2614 | C++98 | war das Ergebnis von E1.E2 unklar, wenn E2 ein Referenzmember oder ein Enumerator ist | wurde klargestellt |
| CWG 2725 | C++98 | wenn E2 eine statische Memberfunktion ist, ist E1.E2 wohlgeformt auch wenn es nicht der linke Operand von operator() ist |
E1.E2 ist schlecht geformt in diesem Fall |
| CWG 2748 | C++98 | war das Verhalten von E1->E2 unklar, wenn E1 ein Nullzeiger ist und E2 auf ein statisches Mitglied verweist |
Das Verhalten ist in diesem Fall nicht definiert. |
| CWG 2813 | C++98 | war E1 keine verworfene Ausdrucksweise, wenn E1.E2 ein statisches Mitglied oder eine Enumeration benennt |
sie ist |
| CWG 2823 | C++98 | war das Verhalten von *expr unklar, wenn expr nicht auf ein Objekt oder eine Funktion zeigt |
wurde klargestellt |
[bearbeiten] Siehe auch
| Häufige Operatoren | ||||||
|---|---|---|---|---|---|---|
| Zuweisung | Inkrement Dekrement |
Arithmetik | Logisch | Vergleich | Member Zugriff |
Sonstiges |
|
a = b |
++a |
+a |
!a |
a == b |
a[...] |
Funktionsaufruf a(...) |
| Komma a, b | ||||||
| Ternär a ? b : c | ||||||
| Spezielle Operatoren | ||||||
|
static_cast konvertiert einen Typ in einen anderen verwandten Typ | ||||||
| C-Dokumentation für Member-Zugriffsoperatoren
|