Amis
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)