Friend declaration
La déclaration friend apparaît dans un corps de classe et accorde à une fonction ou à une autre classe l'accès aux membres privés et protégés de la classe où la déclaration friend apparaît.
Table des matières |
Syntaxe
friend
déclaration-de-fonction
|
(1) | ||||||||
friend
définition-de-fonction
|
(2) | ||||||||
friend
spécificateur-de-type-élaboré
;
|
(3) | (jusqu'en C++26) | |||||||
friend
spécificateur-de-type-simple
;
|
(4) |
(depuis C++11)
(jusqu'en C++26) |
|||||||
friend
liste-spécificateurs-type-ami
;
|
(5) | (depuis C++26) | |||||||
| function-declaration | - | une déclaration de fonction |
| function-definition | - | une définition de fonction |
| elaborated-type-specifier | - | un spécificateur de type élaboré |
| simple-type-specifier | - | un spécificateur de type simple |
| typename-specifier | - | le mot-clé typename suivi d'un identifiant qualifié ou d'un identifiant de modèle simple qualifié |
| friend-type-specifier-list | - |
une liste non-vide séparée par des virgules de
simple-type-specifier
,
elaborated-type-specifier
, et
typename-specifier
s, chaque spécificateur pouvant être suivi par des points de suspension (
...
)
|
Description
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 };
Amis de modèles
Les déclarations de
function template
et de
class template
peuvent apparaître avec le spécificateur
friend
dans toute classe ou class template non locale (bien que seuls les function templates puissent être définis dans la classe ou class template qui accorde l'amitié). Dans ce cas, chaque spécialisation du template devient un ami, qu'elle soit instanciée implicitement, partiellement spécialisée ou explicitement spécialisée.
class A { template<typename T> friend class B; // chaque B<T> est un ami de A template<typename T> friend void f(T) {} // chaque f<T> est un ami de A };
Les déclarations friend ne peuvent pas faire référence à des spécialisations partielles, mais peuvent faire référence à des spécialisations complètes :
template<class T> class A {}; // primaire template<class T> class A<T*> {}; // partielle template<> class A<int> {}; // complète class X { template<class T> friend class A<T*>; // Erreur friend class A<int>; // OK };
Lorsqu'une déclaration friend fait référence à une spécialisation complète d'un modèle de fonction, les mots-clés inline , constexpr (depuis C++11) , consteval (depuis C++20) et les arguments par défaut ne peuvent pas être utilisés :
template<class T> void f(int); template<> void f<int>(int); class X { friend void f<int>(int x = 1); // erreur : arguments par défaut non autorisés };
Une déclaration d'ami template peut nommer un membre d'un template de classe A, qui peut être soit une fonction membre soit un type membre (le type doit utiliser un
spécificateur de type élaboré
). Une telle déclaration n'est bien formée que si le dernier composant de son spécificateur de nom imbriqué (le nom à gauche du dernier
::
) est un simple-template-id (nom du template suivi d'une liste d'arguments entre chevrons) qui nomme le template de classe. Les paramètres template d'une telle déclaration d'ami template doivent être déductibles à partir du simple-template-id.
Dans ce cas, le membre de toute spécialisation de A ou des spécialisations partielles de A devient un ami. Cela n'implique pas l'instanciation du modèle primaire A ou des spécialisations partielles de A : les seules exigences sont que la déduction des paramètres de template de A à partir de cette spécialisation réussisse, et que la substitution des arguments de template déduits dans la déclaration friend produise une déclaration qui serait une redéclaration valide du membre de la spécialisation :
// modèle primaire template<class T> struct A { struct B {}; void f(); struct D { void g(); }; T h(); template<T U> T i(); }; // spécialisation complète template<> struct A<int> { struct B {}; int f(); struct D { void g(); }; template<int U> int i(); }; // autre spécialisation complète template<> struct A<float*> { int *h(); }; // la classe non-modèle accordant l'amitié aux membres du modèle de classe A class X { template<class T> friend struct A<T>::B; // tous les A<T>::B sont amis, y compris A<int>::B template<class T> friend void A<T>::f(); // A<int>::f() n'est pas ami car sa signature // ne correspond pas, mais par exemple A<char>::f() est ami // template<class T> // friend void A<T>::D::g(); // mal formé, la dernière partie du spécificateur de nom imbriqué, // // D dans A<T>::D::, n'est pas un simple-template-id template<class T> friend int* A<T*>::h(); // tous les A<T*>::h sont amis : // A<float*>::h(), A<int*>::h(), etc template<class T> template<T U> // toutes les instanciations de A<T>::i() et A<int>::i() sont amies, friend T A<T>::i(); // et par conséquent toutes les spécialisations de ces modèles de fonction };
|
Les arguments de template par défaut ne sont autorisés dans les déclarations de fonctions amies template que si la déclaration est une définition et qu'aucune autre déclaration de cette fonction template n'apparaît dans cette unité de traduction. |
(depuis C++11) |
Opérateurs friend template
Un cas d'utilisation courant pour les fonctions amies templates est la déclaration d'une surcharge d'opérateur non-membre qui agit sur un modèle de classe, par exemple operator << ( std:: ostream & , const Foo < T > & ) pour un Foo < T > défini par l'utilisateur.
Un tel opérateur peut être défini dans le corps de la classe, ce qui a pour effet de générer un
operator
<<
non-template distinct pour chaque
T
et fait de ce
operator
<<
non-template un ami de sa
Foo
<
T
>
:
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // génère un operator<< non-template pour ce 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'; }
Sortie :
1.23
ou la fonction modèle doit être déclarée comme un modèle avant le corps de la classe, auquel cas la déclaration d'amitié dans
Foo
<
T
>
peut se référer à la spécialisation complète de
operator
<<
pour son
T
:
#include <iostream> template<typename T> class Foo; // déclaration anticipée pour permettre la déclaration de fonction template<typename T> // déclaration std::ostream& operator<<(std::ostream&, const Foo<T>&); template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // fait référence à une spécialisation complète pour ce T particulier friend std::ostream& operator<< <> (std::ostream&, const Foo&); // note : cela repose sur la déduction d'arguments template dans les déclarations // peut également spécifier l'argument template avec operator<< <T> }; // définition 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'; }
Liaison
Les spécificateurs de classe de stockage ne sont pas autorisés dans les déclarations friend.
|
Si une fonction ou un modèle de fonction est d'abord déclaré et défini dans une déclaration friend, et que la classe englobante est définie dans une déclaration d'exportation , son nom a la même liaison que le nom de la classe englobante. |
(since C++20) |
Si (jusqu'en C++20) Sinon, si (depuis C++20) une fonction ou un modèle de fonction est déclaré dans une déclaration friend, et qu'une déclaration non-friend correspondante est accessible, le nom possède la liaison déterminée par cette déclaration antérieure.
Sinon, la liaison du nom introduit par une déclaration friend est déterminée comme d'habitude.
Notes
L'amitié n'est pas transitive (l'ami de votre ami n'est pas votre ami).
L'amitié n'est pas héritée (les enfants de vos amis ne sont pas vos amis, et vos amis ne sont pas les amis de vos enfants).
Spécificateurs d'accès n'ont aucun effet sur la signification des déclarations friend (elles peuvent apparaître dans private : ou dans public : sections, sans aucune différence).
Une déclaration de classe friend ne peut pas définir une nouvelle classe ( friend class X { } ; est une erreur).
Lorsqu'une classe locale déclare une fonction ou une classe non qualifiée comme ami, seules les fonctions et classes dans la portée non-classe la plus interne sont recherchées , et non les fonctions globales :
class F {}; int f(); int main() { extern int g(); class Local // Classe locale dans la fonction main() { friend int f(); // Erreur, aucune fonction de ce type déclarée dans main() friend int g(); // OK, il y a une déclaration pour g dans main() friend class F; // déclare ami une F locale (définie plus tard) friend class ::F; // déclare ami la F globale }; class F {}; // F locale }
Un nom d'abord déclaré dans une déclaration friend à l'intérieur d'une classe ou d'un modèle de classe
X
devient un membre de l'espace de noms englobant le plus interne de
X
, mais n'est pas visible pour la recherche (sauf pour la recherche dépendante des arguments qui considère
X
) à moins qu'une déclaration correspondante au niveau de l'espace de noms soit fournie - voir
namespaces
pour plus de détails.
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_variadic_friend
|
202403L
|
(C++26) | Déclarations friend variadiques |
Mots-clés
Exemple
Les opérateurs d'insertion et d'extraction de flux sont souvent déclarés comme des fonctions amies non-membres :
#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'; }
Sortie :
MyClass::id = 6; i = 7 MyClass::id = 6; i = 100 MyClass::id = 9; i = 100
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 45 | C++98 |
les membres d'une classe imbriquée dans une classe amie
de
T
n'ont pas d'accès spécial à
T
|
une classe imbriquée a le même
accès que la classe englobante |
| CWG 500 | C++98 |
une classe amie de
T
ne peut pas hériter des membres privés ou
protégés de
T
, mais sa classe imbriquée le peut
|
les deux peuvent hériter
de ces membres |
| CWG 1439 | C++98 |
la règle visant les déclarations d'amitié dans les classes non locales
ne couvrait pas les déclarations de templates |
couvert |
| CWG 1477 | C++98 |
un nom d'abord déclaré dans une déclaration d'amitié à l'intérieur d'une classe
ou d'un template de classe n'était pas visible pour la recherche si la déclaration correspondante est fournie dans une autre portée de namespace |
il est visible pour
la recherche dans ce cas |
| CWG 1804 | C++98 |
lorsqu'un membre d'un template de classe est déclaré ami, le membre correspondant
des spécialisations des spécialisations partielles du template de classe n'était pas un ami de la classe accordant l'amitié |
ces membres
sont aussi amis |
| CWG 2379 | C++11 |
les déclarations d'amitié référençant les spécialisations complètes
des templates de fonction pouvaient être déclarées constexpr |
interdit |
| CWG 2588 | C++98 | les linkages des noms introduits par les déclarations d'amitié étaient peu clairs | clarifié |
Références
- Norme C++23 (ISO/CEI 14882:2024) :
-
- 11.8.4 Amis [class.friend]
-
- 13.7.5 Amis [temp.friend]
- Norme C++20 (ISO/CEI 14882:2020) :
-
- 11.9.3 Amis [class.friend]
-
- 13.7.4 Amis [temp.friend]
- Norme C++17 (ISO/IEC 14882:2017) :
-
- 14.3 Amis [class.friend]
-
- 17.5.4 Amis [temp.friend]
- Norme C++14 (ISO/CEI 14882:2014) :
-
- 11.3 Amis [class.friend]
-
- 14.5.4 Amis [temp.friend]
- Norme C++11 (ISO/IEC 14882:2011) :
-
- 11.3 Amis [class.friend]
-
- 14.5.4 Amis [temp.friend]
- Norme C++98 (ISO/CEI 14882:1998) :
-
- 11.3 Amis [class.friend]
-
- 14.5.3 Amis [temp.friend]
Voir aussi
| Class types | définit les types contenant plusieurs membres de données |
| Access specifiers | définit la visibilité des membres de classe |