Amis

Décembre 2016

Amis (friend)

Quand vous déclarez une fonction membre ordinaire, celle-ci a les trois propriétés suivantes :
  • Elle peut accéder à la partie privée de la déclaration de classe.
  • Elle se trouve dans la portée de la classe.
  • Elle doit être appelée sur un objet (le compilateur lui transmet le pointeur this).


Quand vous déclarez une fonction membre statique , celle-ci ne possède que les deux premières propriétés. Si vous déclarez une fonction membre amie (mot clé friend), elle ne possède que la première propriété.

Une fonction membre amie de deux classes ayant accès à la partie privée de ces deux classes, vous pouvez définir, en particulier, des opérateurs opérant sur les deux types d’objet. Dans le code 5.4, par exemple, l’opérateur ami << est déclaré dans les deux classes puis surchargé pour être utilisé avec un objet Date ou avec un objet Heure. Dans notre exemple de code 5.4, la fonction Affiche() pouvait recevoir en argument un objet Date ou un objet Heure, mais nous pourrions tout aussi bien en définir une autre version qui opère sur les deux types d’objets à la fois comme illustré au code 5.5.

Dans le fichier 05-03.h, il est nécessaire de déclarer la classe Heure avant de définir la classe Date car cette dernière comprend la déclaration de l’opérateur ami ostream& operator<< qui agit sur des objets appartenant à ces deux classes. Si vous omettez cette ligne, une erreur va signaler que Heure n’appartient à aucun type dans la déclaration de l’opérateur.



Pour simplifier l’analyse de cet exemple dont la première moitié de code est identique à celle de l’exemple du code 5.4, nous n’avons conservé que les commentaires pour signaler la présence des définitions manquantes.

Code 5.5 : utilisation d’un opérateur ami

1 : #include"05-03.h"  //Inclusion de la déclaration de Date   
2 :                    // et Heure   
3 :   
4 : /*Recopier les définitions suivantes dans le Code 5.4*/   
5 : /****Constructeurs et destructeurs de Date et Heure****/   
6 : /****opérateur << surchargé pour afficher une date ****/   
7 : /****opérateur << surchargé pour afficher une heure ****/   
8 :   
9 : //déclaration du modèle avec ses paramètres (T1 et T2):   
10: template <class T1, class T2>   
11: //déclaration de la fonction "paramétrée" :   
12: void Affiche(T1 x, T2 y)   
13: {   
14: //on affiche les 2 objets :   
15:   cout << "le " << x << " à " << y << endl;   
16: }   
17:   
18: int main()   
19: {   
20:   int j,m,a,hh,mm;   
21:   cout << "nSaisissez la date du RV (ex: 2 12 2010): ";   
22:   cin >> j;   
23:   cin >> m;   
24:   cin >> a;   
25: Date date_RV(j,m,a); //on crée une instance de Date   
26:   cout << "nSaisissez l’heure du RV (ex: 14 45): ";   
27:   cin >> hh;   
27:   cin >> mm;   
28: Heure heure_RV(hh,mm); //puis une instance de Heure   
29:   cout << "nSeule une fonction utilisant un opérateur d’affichage ami des 2 classes peut afficher la date et l’heure du RV:" << endl;   
30:   Affiche(date_RV, heure_RV);   
31:}


L’exécution de ce programme donne le résultat suivant :

Saisissez la date du RV (ex: 2 12 2010): 10 2 2009   
Saisissez l’heure du RV (ex: 14 45): 15 45   

Seule une fonction utilisant un opérateur d’affichage ami des 2 classes peut afficher la date et l’heure du RV:   
le 2 10 2009 à 15h45mn

Amis et modèles

Si nous voulons pouvoir utiliser notre opérateur ami << pour afficher les tableaux d’objets de l’exemple du code 5.1, nous devons faire en sorte qu’il puisse afficher n’importe quel type de Tableau d’objets.

La solution consiste à déclarer operator<< comme fonction amie dans la déclaration du modèle de classe Tableau (voir code 5.6, ligne 54), puis à implémenter cette fonction modèle dans le programme (voir code 5.7, ligne 42).

Les modèles peuvent être manipulés comme n’importe quel autre type de donnée. Vous pouvez les transmettre comme arguments de fonction par référence ou par valeur, et vous pouvez les renvoyer comme valeur de retour toujours par valeur ou par référence. L’exemple qui suit illustre la transmission d’un objet de la classe Tableau comme argument de fonction.

Code 5.6 : utilisation d’un opérateur (fonction) modèle et ami et transmission d’un modèle en argument (fichier 05-06.h)

1 :#include<iostream>      //Pour les entrées/sorties   
2 :using namespace std;   
3 :   
4 ://déclaration de la classe Date   
5 :class Date   
6 :{   
7 :private:   
8 :   int jour, mois, annee;   
9 : public:   
10:   Date();              //constructeur par défaut   
12:   Date(int,int,int);         //Constructeur   
13:   ~Date();                   //Destructeur   
14:   
15:   //méthodes d’accès   
16:   void DefinirDate(int j,int m,int a)   
17:     { jour=j; mois=m; annee=a; }   
18:   int LireJour() const { return jour; }   
19:   int LireMois() const { return mois; }   
20:   int LireAnnee() const { return annee; }   
21:   
22:  //opérateur = surchargé pour Date   
23:  Date operator=(const Date);   
24:   
25: };   
26:      
27: //déclaration du modèle avec son type paramètre (T)   
28: template <class T>   
29:    
30: //déclaration de la classe "paramètre"   
31:class Tableau         
32:{   
33:private:   
34:   T * p_tab;  //déclaration du pointeur de tableau p_tab   
35:   int taille; //autre élément important d’un tableau   
36:   
37:public:   
38:   //Constructeur avec argument par défaut   
39:   Tableau(int taille);   
40:   //Constructeur de copie   
41:   Tableau(const Tableau &source);   
42:   //Destructeur   
43:   ~Tableau() { delete [] p_tab; }   
44:   
45:   //surcharge de l’opérateur = :   
46:   Tableau& operator=(const Tableau&);   
47:   //surcharge de l’opérateur [] :   
48:   T& operator[] (int index) { return p_tab[index]; }   
49:   const T& operator[](int index) const   
50:    { return p_tab[index]; }   
51: int LireTaille() const { return taille; }   
52:   
53:   //fonction amie   
54:   friend ostream& operator<< (const Date&, const Tableau<T>&);   
55 :};

Code 5.7 : utilisation d’un opérateur (fonction) modèle et ami et transmission d’un modèle en argument (fichier 05-06.cpp)

1 : #include"05-06.h"   
2 : const int taille_defaut=4;   
3 :/****Constructeurs et destructeur de Date****/   
4 :Date::Date()   
5 :  { jour=0; mois=0; annee=0; }   
6 :Date::Date(int j,int m,int a)   
7 :{ jour=j; mois=m; annee=a; }   
8 :   
9 :Date::~Date() { }   
10:   
12:/****opérateur << surchargé pour afficher une date ****/   
13: ostream& operator<< (ostream& sortie, const Date une_date)   
14:{   
15:   sortie << une_date.LireJour() << " ";   
16:   sortie << une_date.LireMois() << " " ;   
17:   sortie << une_date.LireAnnee();   
18:   return sortie;   
19:}   
20:   
21: /**Définition de l’opérateur = surchargé pour Date**/   
22:    
23: Date Date::operator=(const Date source)   
24: {   
25:   if (this == &source) //si les deux objets sont égaux   
26:     return *this;    //on renvoie tout de suite l’objet   
27:     jour = source.LireJour();   //sinon on copie jour   
28:     mois = source.LireMois();   //puis mois   
29:     annee = source.LireAnnee(); //et enfin annee   
30:     return source;   
31: }   
32:   
33:/*Définition du constructeur du modèle   
34:    de classe Tableau*/   
35: template <class T>   
36: Tableau<T>::Tableau(int t=taille_defaut):taille(t)   
37: {   
38:   p_tab = new T[t];   
39: }   
40:   
41:/****La fonction operator<< amie et modèle****/   
42: template <class T>   
43: ostream& operator<< (ostream& sortie, const Tableau<T>& un_tableau)   
44: {   
45:    for(int i=0; i<un_tableau.LireTaille(); i++)   
46:      sortie << i+1 << " : " << un_tableau[i] << endl;   
47:   return sortie;   
48: }   
49:   
50://prototype de l’affichage d’un tableau de dates   
51:void ListeRV(Tableau<Date>& un_tableau);   
52:   
53:int main()   
54:{   
55:   Tableau<Date> mon_tableau;   
56:   ListeRV(mon_tableau);   
57:}   
58:   
59:/*définition de la fonction d’affichage d’un tableau   
60:   de dates*/   
61: void ListeRV(Tableau<Date>& un_tableau)   
62: {   
63:   Date *p_date;   
64:   cout << "Saisissez les dates de Rendez-vous (ex 5 12 2009): " << endl;   
65:   //on initialise les éléments du tableau   
66:   for (int i=0; i<un_tableau.LireTaille(); i++)     
67:   {   
68:      int j, m, a;   
69:      cout << "ttDate "<< i+1 <<": ";   
70:      cin >> j;                                   
71:      cin >> m;                                    
72:      cin >> a;   
73:      p_date = new Date(j, m, a); //on réserve la mémoire   
74:      un_tableau[i] = *p_date;    //on stocke sa valeur à   
75:                                // l’emplacement réservé   
76:      delete p_date;      //le tableau contient une copie   
77:    }   
78:      //maintenant on les affiche   
79:    cout << un_tableau << endl ;   
80: }


L’exécution de ce programme donne le résultat suivant :

Saisissez les dates de Rendez-vous (ex 5 12 2009):   
                Date 1 : 5 1 2010   
                Date 2 : 20 12 2010   
                Date 3 : 22 5 2005   
                Date 4 : 10 9 2010   
1 : 5 1 2010   
2 : 20 12 2010   
3 : 22 5 2005   
4 : 10 9 2010


La fonction principale est très courte, elle crée un objet tableau de dates qu’elle transmet ensuite comme argument de la fonction ListeRV()
.

Classes amies

Pour rendre toutes les méthodes d’une classe amie d’une autre classe, il suffit de déclarer la classe complète comme étant amie. On code cette fois le mot clé friend avant la déclaration de la classe, à l’intérieur de la classe cible.

Code 5.8 : classe amie

#include<iostream>      //Pour les entrées/sorties   
using namespace std;   

/* Déclaration de la classe principale */   
class UneClasse   
{   
   friend class Amie;    
// Toutes les méthodes de Amie sont amies   

   int i;           // Donnée privée de UneClasse   

public:   
   UneClasse(void) { i=5; return ; } //constructeur   
   int LireObjet() const { return i; }   
};   

/* Déclaration de la classe amie */   
class Amie   
{   
public:   
   void AfficherObjet(UneClasse un_objet)   
// on affiche la donnée privée de objet   
   { cout << un_objet.LireObjet(); }   
};   

int main()   
{   
   UneClasse objet;   //on crée un objet de type UneClasse   
   Amie a;            //on crée un objet de type Amie   
   cout << "L’objet a la valeur: ";   
   a.AfficherObjet(objet);  //on affiche la valeur de objet via a   
}


On obtient :

L’objet a la valeur: 5


*L’amitié n’est pas transitive. Cela signifie que les amis des amis ne sont pas des amis. Une classe A amie d’une classe B, elle-même amie d’une classe C, n’est pas amie de la classe C par défaut. Il faut la déclarer amie explicitement si on désire qu’elle le soit.

  • Les amis ne sont pas hérités. Ainsi, si une classe A est amie d’une classe B et que la classe C soit une classe fille de la classe B, alors A n’est pas amie de la classe C par défaut. Une fois encore, il faut la déclarer amie explicitement.


Ces remarques s’appliquent également aux fonctions amies.



Le texte original de cette fiche pratique est extrait de
«Tout sur le C++» (Christine EBERHARDT, Collection
CommentCaMarche.net, Dunod, 2009)

A voir également :

Ce document intitulé «  Amis  » issu de CommentCaMarche (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.