Friend-Deklaration
Die Friend-Deklaration erscheint im Klassenkörper und gewährt einer Funktion oder einer anderen Klasse Zugriff auf private und protected Member der Klasse, in der die Friend-Deklaration erscheint.
Inhalt |
[bearbeiten] Syntax
friend Funktionsdeklarator |
(1) | ||||||||
friend Funktionsdefinition |
(2) | ||||||||
friend elaborated-type-specifier ; |
(3) | (bis C++26) | |||||||
friend simple-type-specifier ;
|
(4) | (seit C++11) (bis C++26) | |||||||
friend friend-type-specifier-list ; |
(5) | (seit C++26) | |||||||
| funktionsdeklarator | - | eine Funktionsdeklaration |
| funktionsdefinition | - | eine Funktionsdefinition |
| elaborated-type-specifier | - | ein elaborated type specifier |
| simple-type-specifier | - | ein simple type specifier |
| typename-specifier | - | das Schlüsselwort typename gefolgt von einem qualifizierten Bezeichner oder einem qualifizierten einfachen Template-Bezeichner |
| friend-type-specifier-list | - | eine nicht-leere, durch Kommas getrennte Liste von simple-type-specifier, elaborated-type-specifier und typename-specifier, wobei jeder Specifier von einer Ellipse (...) gefolgt sein kann |
[bearbeiten] Beschreibung
class Y { int data; // private member // the non-member function operator<< will have access to Y's private members friend std::ostream& operator<<(std::ostream& out, const Y& o); friend char* X::foo(int); // members of other classes can be friends too friend X::X(char), X::~X(); // constructors and destructors can be friends }; // friend declaration does not declare a member function // this operator<< still needs to be defined, as a non-member std::ostream& operator<<(std::ostream& out, const Y& y) { return out << y.data; // can access private member Y::data }
class X { int a; friend void friend_set(X& p, int i) { p.a = i; // this is a non-member function } public: void member_set(int i) { a = i; // this is a member function } };
class Y {}; class A { int data; // private data member class B {}; // private nested type enum { a = 100 }; // private enumerator friend class X; // friend class forward declaration (elaborated class specifier) friend Y; // friend class declaration (simple type specifier) (since C++11) // the two friend declarations above can be merged since C++26: // friend class X, Y; }; class X : A::B // OK: A::B accessible to friend { A::B mx; // OK: A::B accessible to member of friend class Y { A::B my; // OK: A::B accessible to nested member of friend }; int v[A::a]; // OK: A::a accessible to member of friend };
[bearbeiten] Template-Freunde
Sowohl Funktionstemplates als auch Klassentemplates können mit dem Schlüsselwort friend in jeder nicht-lokalen Klasse oder jedem Klassentemplate deklariert werden (obwohl nur Funktionstemplates innerhalb der Klasse oder des Klassentemplates, das die Freundschaft gewährt, definiert werden dürfen). In diesem Fall wird jede Spezialisierung des Templates zu einem Freund, unabhängig davon, ob sie implizit instanziiert, partiell spezialisiert oder explizit spezialisiert wurde.
class A { template<typename T> friend class B; // every B<T> is a friend of A template<typename T> friend void f(T) {} // every f<T> is a friend of A };
Friend-Deklarationen können nicht auf partielle Spezialisierungen verweisen, aber auf vollständige Spezialisierungen
template<class T> class A {}; // primary template<class T> class A<T*> {}; // partial template<> class A<int> {}; // full class X { template<class T> friend class A<T*>; // Error friend class A<int>; // OK };
Wenn eine Friend-Deklaration auf eine vollständige Spezialisierung eines Funktionstemplate verweist, können die Schlüsselwörter inline, constexpr(seit C++11), consteval(seit C++20) und Standardargumente nicht verwendet werden
template<class T> void f(int); template<> void f<int>(int); class X { friend void f<int>(int x = 1); // error: default args not allowed };
Eine Template-Friend-Deklaration kann ein Mitglied eines Klassentemplates A benennen, das entweder eine Member-Funktion oder ein Member-Typ sein kann (der Typ muss elaborated-type-specifier verwenden). Solch eine Deklaration ist nur dann wohlgeformt, wenn die letzte Komponente im nested-name-specifier (der Name links vom letzten ::) ein simple-template-id ist (Template-Name gefolgt von Argumentenliste in spitzen Klammern), das das Klassentemplate benennt. Die Template-Parameter einer solchen Template-Friend-Deklaration müssen aus dem simple-template-id ableitbar sein.
In diesem Fall wird das Mitglied jeder Spezialisierung von entweder A oder partiellen Spezialisierungen von A ein Freund. Dies beinhaltet nicht die Instanziierung des primären Templates A oder partieller Spezialisierungen von A: Die einzigen Anforderungen sind, dass die Ableitung der Template-Parameter von A aus dieser Spezialisierung erfolgreich ist und dass die Substitution der abgeleiteten Template-Argumente in die Friend-Deklaration eine Deklaration ergibt, die eine gültige Wiederholung des Mitglieds der Spezialisierung wäre.
// primary template template<class T> struct A { struct B {}; void f(); struct D { void g(); }; T h(); template<T U> T i(); }; // full specialization template<> struct A<int> { struct B {}; int f(); struct D { void g(); }; template<int U> int i(); }; // another full specialization template<> struct A<float*> { int *h(); }; // the non-template class granting friendship to members of class template A class X { template<class T> friend struct A<T>::B; // all A<T>::B are friends, including A<int>::B template<class T> friend void A<T>::f(); // A<int>::f() is not a friend because its signature // does not match, but e.g. A<char>::f() is a friend // template<class T> // friend void A<T>::D::g(); // ill-formed, the last part of the nested-name-specifier, // // D in A<T>::D::, is not simple-template-id template<class T> friend int* A<T*>::h(); // all A<T*>::h are friends: // A<float*>::h(), A<int*>::h(), etc template<class T> template<T U> // all instantiations of A<T>::i() and A<int>::i() are friends, friend T A<T>::i(); // and thereby all specializations of those function templates };
|
Standard-Template-Argumente sind nur bei Template-Friend-Deklarationen erlaubt, wenn die Deklaration eine Definition ist und keine anderen Deklarationen dieses Funktionstemplate in dieser Translation Unit erscheinen. |
(seit C++11) |
[bearbeiten] Template-Friend-Operatoren
Ein gängiger Anwendungsfall für Template-Freunde ist die Deklaration einer Nicht-Member-Operatorüberladung, die auf ein Klassentemplate wirkt, z. B. operator<<(std::ostream&, const Foo<T>&) für ein benutzerdefiniertes Foo<T>.
Ein solcher Operator kann im Klassenkörper definiert werden, was den Effekt hat, dass für jedes T ein separater Nicht-Template operator<< generiert wird und dieser Nicht-Template operator<< zu einem Freund seines Foo<T> wird
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // generates a non-template operator<< for this T friend std::ostream& operator<<(std::ostream& os, const Foo& obj) { return os << obj.data; } }; int main() { Foo<double> obj(1.23); std::cout << obj << '\n'; }
Ausgabe
1.23
oder das Funktionstemplates muss als Template vor dem Klassenkörper deklariert werden, in welchem Fall die Friend-Deklaration innerhalb von Foo<T> auf die vollständige Spezialisierung von operator<< für sein T verweisen kann
#include <iostream> template<typename T> class Foo; // forward declare to make function declaration possible template<typename T> // declaration std::ostream& operator<<(std::ostream&, const Foo<T>&); template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // refers to a full specialization for this particular T friend std::ostream& operator<< <> (std::ostream&, const Foo&); // note: this relies on template argument deduction in declarations // can also specify the template argument with operator<< <T>" }; // definition template<typename T> std::ostream& operator<<(std::ostream& os, const Foo<T>& obj) { return os << obj.data; } int main() { Foo<double> obj(1.23); std::cout << obj << '\n'; }
[bearbeiten] Linkage
Storage-Klassenspezifizierer sind in Friend-Deklarationen nicht erlaubt.
|
Wenn eine Funktion oder ein Funktionstemplates zuerst in einer Friend-Deklaration deklariert und definiert wird und die umschließende Klasse innerhalb von exportierten Deklarationen definiert ist, hat ihr Name die gleiche Linkage wie der Name der umschließenden Klasse. |
(seit C++20) |
Wenn(bis C++20)Andernfalls, wenn(seit C++20) eine Funktion oder ein Funktionstemplates in einer Friend-Deklaration deklariert wird und eine entsprechende Nicht-Friend-Deklaration erreichbar ist, hat der Name die Linkage, die aus dieser vorherigen Deklaration bestimmt wird.
Andernfalls wird die Linkage des Namens, der durch eine Friend-Deklaration eingeführt wird, wie üblich bestimmt.
[bearbeiten] Notizen
Freundschaft ist nicht transitiv (ein Freund deines Freundes ist nicht dein Freund).
Freundschaft ist nicht vererbbar (die Kinder deines Freundes sind nicht deine Freunde, und deine Freunde sind nicht die Freunde deiner Kinder).
Zugriffsspezifizierer haben keine Auswirkung auf die Bedeutung von Friend-Deklarationen (sie können in private: oder in public: Abschnitten erscheinen, ohne Unterschied).
Eine Friend-Klassendeklaration kann keine neue Klasse definieren (friend class X {}; ist ein Fehler).
Wenn eine lokale Klasse eine nicht qualifizierte Funktion oder Klasse als Freund deklariert, werden nur Funktionen und Klassen im innersten nicht-Klassen-Gültigkeitsbereich gesucht, nicht aber globale Funktionen.
class F {}; int f(); int main() { extern int g(); class Local // Local class in the main() function { friend int f(); // Error, no such function declared in main() friend int g(); // OK, there is a declaration for g in main() friend class F; // friends a local F (defined later) friend class ::F; // friends the global F }; class F {}; // local F }
Ein Name, der zuerst in einer Friend-Deklaration innerhalb einer Klasse oder eines Klassentemplates X deklariert wird, wird Mitglied des innersten umschließenden Namensraums von X, ist aber für die Suche nicht sichtbar (außer bei Argument-abhängiger Suche, die X berücksichtigt), es sei denn, eine übereinstimmende Deklaration im Namensraum wird bereitgestellt - siehe Namensräume für Details.
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_variadic_friend |
202403L |
(C++26) | Variadische Friend-Deklarationen |
[bearbeiten] Schlüsselwörter
[bearbeiten] Beispiel
Stream-Einfüge- und Extraktionsoperatoren werden oft als Nicht-Member-Freunde deklariert
#include <iostream> #include <sstream> class MyClass { int i; // friends have access to non-public, non-static static inline int id{6}; // and static (possibly inline) members friend std::ostream& operator<<(std::ostream& out, const MyClass&); friend std::istream& operator>>(std::istream& in, MyClass&); friend void change_id(int); public: MyClass(int i = 0) : i(i) {} }; std::ostream& operator<<(std::ostream& out, const MyClass& mc) { return out << "MyClass::id = " << MyClass::id << "; i = " << mc.i; } std::istream& operator>>(std::istream& in, MyClass& mc) { return in >> mc.i; } void change_id(int id) { MyClass::id = id; } int main() { MyClass mc(7); std::cout << mc << '\n'; // mc.i = 333*2; // error: i is a private member std::istringstream("100") >> mc; std::cout << mc << '\n'; // MyClass::id = 222*3; // error: id is a private member change_id(9); std::cout << mc << '\n'; }
Ausgabe
MyClass::id = 6; i = 7 MyClass::id = 6; i = 100 MyClass::id = 9; i = 100
[bearbeiten] 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 45 | C++98 | Mitglieder einer Klasse, die in einem Freund verschachtelt ist Klasse von T haben keinen besonderen Zugriff auf T |
eine verschachtelte Klasse hat die gleiche Zugriffsberechtigung wie die umschließende Klasse |
| CWG 500 | C++98 | Friend-Klasse von T kann nicht von privaten oderprotected-Mitgliedern von T erben, aber ihre verschachtelte Klasse kann |
beide können erben von solchen Mitgliedern |
| CWG 1439 | C++98 | die Regel, die sich auf Friend-Deklarationen in nicht-lokalen Klassen bezieht, deckte keine Template-Deklarationen ab |
abgedeckt |
| CWG 1477 | C++98 | ein Name, der zuerst in einer Friend-Deklaration innerhalb einer Klasse oder eines Klassentemplates deklariert wurde, war für die Suche nicht sichtbar, wenn die übereinstimmende Deklaration in einem anderen Namensraum-Gültigkeitsbereich bereitgestellt wird |
sie ist für die Suche in diesem Fall sichtbar |
| CWG 1804 | C++98 | wenn ein Mitglied eines Klassentemplates befreundet wird, das entsprechende Mitglied von Spezialisierungen von partiellen Spezialisierungen des Klassentemplates war kein Freund der Klasse, die Freundschaft gewährt |
solche Mitglieder sind ebenfalls Freunde |
| CWG 2379 | C++11 | Friend-Deklarationen, die sich auf vollständige Spezialisierungen von Funktionstemplates beziehen, könnten constexpr deklariert werden |
verboten |
| CWG 2588 | C++98 | die Linkages von Namen, die durch Friend-Deklarationen eingeführt wurden, waren unklar | wurde klargestellt |
[bearbeiten] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 11.8.4 Friends [class.friend]
- 13.7.5 Friends [temp.friend]
- C++20 Standard (ISO/IEC 14882:2020)
- 11.9.3 Friends [class.friend]
- 13.7.4 Friends [temp.friend]
- C++17 Standard (ISO/IEC 14882:2017)
- 14.3 Friends [class.friend]
- 17.5.4 Friends [temp.friend]
- C++14 Standard (ISO/IEC 14882:2014)
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- C++11 Standard (ISO/IEC 14882:2011)
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- C++98 Standard (ISO/IEC 14882:1998)
- 11.3 Friends [class.friend]
- 14.5.3 Friends [temp.friend]
[bearbeiten] Siehe auch
| Klassentypen | definiert Typen, die mehrere Datenmitglieder enthalten |
| Zugriffsspezifizierer | definiert die Sichtbarkeit von Klassenmitgliedern |