FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- Que signifie « friend » ?
- Les amis brisent-ils l'encapsulation ?
- Quels avantages/désavantages y a-t-il à utiliser des fonctions friend ?
- Que signifie « l'amitié n'est ni héritée ni transitive, ni réciproque » ?
- Doit-on utiliser plutôt des fonctions membres ou plutôt des fonctions friend ?
- Comment déclarer une fonction amie avec une classe template ?
- Comment déclarer amies l'ensemble des classes issues d'un même modèle ?
Quelque chose qui permet à une classe d'offrir des droits d'accès privilégiés à une autre classe ou fonction.
Les amis (friends) peuvent être soit des fonctions, soit d'autres classes. Une classe offre des droits d'accès privilégiés à ses amis. Le développeur d'une classe exerce en théorie un contrôle technique et politique aussi bien sur les friends que sur les fonctions membres de la classe (si ce n'était pas le cas, il lui faudrait obtenir une autorisation de ceux qui ont écrits des amis lorsqu'il souhaite modifier sa classe).
C'est tout le contraire : s'ils sont utilisés correctement, ils renforcent l'encapsulation.
Il est souvent nécessaire de séparer une classe en deux, par exemple quand les deux moitiés ne peuvent pas avoir le même nombre d'instances ou quand elles n'ont pas la même durée de vie. Dans ce genre de cas, les deux moitiés ont généralement besoin de pouvoir accéder directement l'une à l'autre (dans la mesure où ces deux moitiés appartenaient à une unique classe, le nombre de lignes de code nécessitant un accès direct à la structure de données n'a pas augmenté ; le code a simplement été redistribué entre deux classes plutôt que laissé dans une seule). La façon la plus sûre d'implémenter cela est de rendre les deux moitiés amies l'une de l'autre.
En vous basant sur le modèle ci-dessus pour utiliser les amis, vous garantissez que les parties private restent private. Les gens qui ne comprennent pas ce modèle font souvent des tentatives naïves pour éviter d'utiliser l'amitié dans des situations se rapprochant de celle vue au-dessus. Ces tentatives vont en fait le plus souvent briser l'encapsulation. Soit ces personnes utilisent des données publiques (c'est grotesque !), soit elles offrent un accès aux données à travers des fonctions membres public de type get()/set(). Il n'y a pas de problème à avoir des fonctions membres public de type get()/set() qui donnent accès à des données private, mais seulement quand accéder à ces données private « a un sens » du point de vue de l'extérieur de la classe (du point de vue de l'utilisateur). Dans de nombreux cas, à utiliser des fonctions membres get()/set() s'avère presque aussi mauvais que d'utiliser directement des données public : ces fonctions à membres cachent (seulement) le nom de la donnée private, mais elles ne cachent pas son existence.
De façon similaire, utiliser des fonctions amies comme une alternative syntaxique aux fonctions d'accès public : de la classe ne brise pas plus l'encapsulation que le font les fonctions membres de la classe. On pourrait dire que les amis d'une classe ne brisent pas la barrière d'encapsulation : avec les fonctions membres de la classe, ils sont la barrière d'encapsulation.
Elles offrent plus de possibilités dans la conception d'une interface.
Les fonctions membres et les fonctions friend ont des droits d'accès identiques (c'est 100% garanti). La différence principale est qu'un appel à une fonction friend est de la forme f(x), alors qu'un appel à une fonction membre est de la forme x.f(). Ainsi, la possibilité qu'a le concepteur de la classe de choisir entre les fonctions membres (x.f()) et les fonctions friend (f(x)) lui permet de sélectionner la syntaxe qu'il estime la plus lisible, ce qui diminuera le coût de maintenance.
Le désavantage principal des fonctions friend est qu'elles nécessitent une ligne de code supplémentaire pour obtenir une liaison dynamique (dynamic binding). Pour simuler un virtual friend, il est nécessaire que la fonction friend appelle une fonction membre virtual cachée (qui est habituellement protected:). C'est ce que l'on appelle l'Idiome de la Fonction Friend Virtuelle. Voici un exemple :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Base { public: friend void f(Base& b); // ... protected: virtual void do_f(); // ... }; inline void f(Base& b) { b.do_f(); } class Derived : public Base { public: // ... protected: virtual void do_f(); // "Redéfinit" le comportement de f(Base& b) // ... }; void userCode(Base& b) { f(b); } |
Ce n'est pas parce que je vous donne accès à ma classe en tant qu'ami que j'autorise vos enfants à y accéder, ni à vos amis, et cela ne me donne pas non plus automatiquement accès à votre classe.
Je ne fais pas forcément confiance aux enfants de mes amis Les privilèges de l'amitié ne sont pas hérités. Les classes dérivées d'une classe amie ne sont pas forcément des amis. Si la classe Fred déclare que la classe Base est une amie, les classes dérivées de Base n'ont pas à avoir automatiquement des droits d'accès particuliers aux objets de type Fred.
Je ne fais pas forcément confiance aux amis de mes amis Les privilèges de l'amitié ne sont pas transitifs. Un ami d'un ami n'est pas forcément un ami. Si la classe Fred déclare que la classe Wilma est une amie, et que la classe Wilma déclare que Betty est une amie, la classe Betty n'a pas à avoir automatiquement des droits d'accès particuliers aux objets de type Fred.
L'amitié n'est pas réciproque Vous ne me faites pas confiance simplement parce que je déclare que vous être mon ami. Les privilèges de l'amitié ne sont pas réciproques. Si la classe Fred déclare que la classe Wilma est une amie, les objets de type Fred n'ont pas à avoir automatiquement des droits d'accès particuliers aux objets de type Wilma.
Utilisez les membres quand vous le pouvez, et les friend quand vous le devez.
Les amis sont parfois un meilleur choix d'un point de vue syntaxique (comme par exemple quand une fonction amie permet à un objet de type Fred d'être utilisé en tant que second paramètre de la fonction, tandis qu'une fonction membre obligerait à ce que l'objet Fred soit en premier). Les opérateurs arithmétiques binaires infixes sont un autre cas où l'utilisation des fonctions friend est appropriée. Par exemple, aComplex + aComplex doit être défini comme un ami plutôt que comme un membre si vous voulez aussi pouvoir écrire aFloat + aComplex (les fonctions membres n'autorisent pas la promotion de l'argument de gauche, la raison étant que cela changerait la classe de l'objet sur lequel on invoque la fonction membre).
Dans les autres cas, utilisez une fonction membre plutôt qu'une fonction friend. De plus; il est préférable d'utiliser au maximum des fonctions libres dans le même namespace.
Cet exemple regroupe tous les cas de fonctions amies d'une classe template.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | template <typename T> struct Foo; // void friendFn1(Foo const&); template <typename T> void friendFn2(Foo<T> const &); template <typename T> void friendFn3(Foo<T> const &); template <typename T> void friendFn4(Foo<T> const &); // void friendFn4(Foo const&); template <typename T> void friendFn5(Foo<int> const &, T const &); template <typename T> struct Foo { friend void friendFn1(Foo const&); // 1 friend void friendFn2<T>(Foo const&); // 2 friend void friendFn3<>(Foo const&); // 3 friend void friendFn4(Foo const&); // 4 template <typename U> friend void friendFn5(Foo const&, U const&); // 5 private: int x; }; void friendFn1(Foo<int> const& f) { f.x; } // 6 template<typename T> void friendFn2(Foo<T> const& f) { f.x; } // 7 template<typename T> void friendFn3(Foo<T> const& f) { f.x; } // 8 template<typename T> void friendFn4(Foo<T> const& f) { f.x; } // 9 void friendFn4(Foo<int> const& f) { f.x; } // 10 template<typename T> void friendFn5(Foo<int> const& f, T const&) { f.x; } // 11 int main() { Foo<int> f; friendFn1(f); // 12 friendFn2(f); // 13 friendFn3(f); // 14 friendFn4<>(f); // 15 friendFn4(f); // 16 friendFn5(f, 1); // 17 } |
(1) déclare comme amie une fonction non template prenant donc une instanciation de la classe comme paramètre. Cette fonction est définie en (6) et appelée en (12).
(2) déclare comme amie la spécialisation d'une fonction template. La fonction est définie en (7) et appelée en (13).
(3) est une syntaxe alternative qui peut être utilisée quand tous les paramètres templates peuvent être déduits des arguments.
(5) déclare comme amies toutes les spécialisations d'une fonction template. La fonction est définie en (11) et appelée en (17).
La subtilité intervient en (4). Le C++ permet de surcharger une fonction template avec une fonction non template. Dans ce cas, il peut y avoir une spécialisation de la fonction template qui a le même prototype que la fonction non template. La fonction non template est alors préférée à la fonction template, mais on peut spécifier qu'il faut appeler la fonction template avec la syntaxe (15).
(4) est de même structure que (1), donc elle déclare comme amie la fonction non template définie en (10) et appelée en (16).
La fonction template de même nom, définie en (9) et appelée en (15) n'est pas amie, donc le programme comme tel ne doit pas compiler tant qu'elle tente d'accéder à un membre privé ou qu'on ne l'a pas déclarée amie avec les syntaxes (2) ou (3).
Comme il est rare qu'on désire déclarer comme amie des fonctions non templates et que la subtilité ci-dessus fait des dégâts, certains compilateurs donnent un avertissement. Ce que font g++ et icc
Code c++ : | Sélectionner tout |
1 2 3 4 | main.cpp:12:35: warning: friend declaration 'void friendFn1(const Foo<T>&)' declares a non-template function [-Wnon-template-friend] friend void friendFn1(Foo const&); // 1 ^ main.cpp:12:35: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) |
Il peut arriver que l'on ait besoin de donner accès aux membres privés d'une classe à l'ensemble des classes issues d'un même modèle.
Dans ce cas, on utilise le mot-clé friend, mais celui-ci comporte une syntaxe spéciale.
Prenons par exemple une classe template B :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | template <class T> class B { // ... }; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | class A { // ... // On définit amies l'ensemble des classes issues du modèle B template<class> friend class B; }; |
Notes :
- Dans le cas où le paramètre template T n'est pas une classe (mais par exemple un int), il faut remplacer <class> par le type correspondant.
- De même, s'il y a plusieurs paramètres templates (par exemple deux classes), il suffit d'ajouter leurs types séparés par une virgule (par exemple <class,class>).
- Cette méthode est particulièrement utile pour que toutes classes issues d'une même classe template aient accès les unes aux autres. Dans le cas de B, on définirait alors :
Code c++ : Sélectionner tout 1
2
3
4
5
6
7template <class T> class B { // ... // On définit amis l'ensemble des autres classes issues de B template<class> friend class B; };
- Cette méthode peut aussi être utilisée avec les fonctions template, par exemple, avec la fonction Exemple :
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11
12
13//Fonction template template<class T> T& Exemple(const T& a,int b) { // ... } class A { // ... // On rend Exemple amie de A template <class T> friend T& Exemple(const T&, int); };
- Il n'est pas possible de spécialiser une amitié template.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.