Jeux Libres
       
           

» Les Tutoriels » Apprenez à programmer en C++ ! » Eléments statiques et amitié

Note : Vous vous apprêtez à lire un tutoriel de M@teo21 et Nanoc initialement publié à cette adresse sous la licence Creative Commons BY-NC-SA 2.0.

Eléments statiques et amitié


Vous tenez le coup ?
Courage, vos efforts seront bientôt largement récompensés. Ce chapitre va d'ailleurs vous permettre de souffler un peu. Vous allez découvrir quelques notions spécifiques aux classes en C++ : les attributs et méthodes statiques, ainsi que l'amitié. Ce sont ce que j'appellerais des « points particuliers » du C++. Ce ne sont pas des détails pour autant, ce sont des choses à connaître.

Car oui, tout ce que je vous apprends là, vous allez en avoir besoin et vous allez largement le réutiliser. Je suis sûr aussi que vous en comprendrez mieux l'intérêt lorsque vous pratiquerez pour de bon.
N'allez pas croire que les programmeurs ont inventé des trucs un peu complexes comme cela, juste pour le plaisir de programmer de façon tordue...





Chapitre précédent     Sommaire


Les méthodes statiques


Les méthodes statiques sont un peu spéciales
Ce sont des méthodes qui appartiennent à la classe mais pas aux objets instanciés à partir de la classe. En fait, ce sont de bêtes « fonctions » rangées dans des classes qui n'ont pas accès aux attributs de la classe. Elles s'utilisent d'une manière un peu particulière.

Je pense que le mieux est encore un exemple !

Créer une méthode statique



Dans le .h, le prototype d'une méthode statique ressemble à ceci :
1
2
3
4
5
6
class MaClasse
{
   public:
   MaClasse();
   static void maMethode();
};

Son implémentation dans le .cpp ne possède pas en revanche de mot-clé static :
1
2
3
4
void MaClasse::maMethode() //Ne pas remettre 'static' dans l'implmentation
{
   cout << "Bonjour !" << endl;
}

Ensuite, dans le main(), la méthode statique s'appelle comme ceci :
1
2
3
4
5
6
int main()
{
   MaClasse::maMethode();
 
   return 0;
}

Mais on n'a pas créé d'objet de type MaClasse et on appelle la méthode quand même ? C'est quoi ce bazar ?


C'est justement cela, la particularité des méthodes statiques. Pour les utiliser, pas besoin de créer un objet. Il suffit de faire précéder le nom de la méthode du nom de la classe suivi d'un double deux-points.
D'où le : MaClasse::maMethode();

Cette méthode, comme je vous le disais, ne peut pas accéder aux attributs de la classe. C'est vraiment une bête fonction mais rangée dans une classe. Cela permet de regrouper les fonctions dans des classes, par thème, et aussi d'éviter des conflits de nom.

Quelques exemples de l'utilité des méthodes statiques



Les méthodes statiques peuvent vous paraître un tantinet stupides. En effet, à quoi bon avoir inventé le modèle objet si c'est pour autoriser les gens à créer de bêtes fonctions regroupées dans des classes ?

La réponse, c'est qu'on a toujours besoin d'utiliser de « bêtes » fonctions, même en modèle objet, et pour être un peu cohérent, on les regroupe dans des classes en précisant qu'elles sont statiques.

Il y a en effet des fonctions qui ne nécessitent pas de créer un objet, pour lesquelles cela n'aurait pas de sens.
Des exemples ?

  • Il existe dans la bibliothèque Qt une classe QDate qui permet de manipuler des dates. On peut comparer des dates entre elles (surcharge d'opérateur) etc. Cette classe propose aussi un certain nombre de méthodes statiques, comme currentDate() qui renvoie la date actuelle. Pas besoin de créer un objet pour avoir cette information ! Il suffit donc de taper QDate::currentDate() pour récupérer la date actuelle.

  • Toujours avec Qt, la classe QDir, qui permet de manipuler les dossiers du disque dur, propose quelques méthodes statiques. Par exemple, on trouve QDir::drives() qui renvoie la liste des disques présents sur l'ordinateur (par exemple « C:\ », « D:\ », etc.). Là encore, cela n'aurait pas d'intérêt d'instancier un objet à partir de la classe car ce sont des informations générales.

  • etc.

J'espère que cela vous donne envie de travailler avec Qt parce que la partie suivante de ce livre y est consacrée ! ;-)


Les attributs statiques


Il existe aussi ce qu'on appelle des attributs statiques.
Tout comme les méthodes statiques, les attributs statiques appartiennent à la classe et non aux objets créés à partir de la classe.

Créer un attribut statique dans une classe



C'est assez simple en fait : il suffit de rajouter le mot-clé static au début de la ligne.
Un attribut static, bien qu'il soit accessible de l'extérieur, peut très bien être déclaré private ou protected. Appelez cela une exception, car c'en est bien une.

Exemple :
1
2
3
4
5
6
7
8
9
class MaClasse
{
   public:
   MaClasse();
 
   private:
   static int monAttribut;
 
};

Sauf qu'on ne peut pas initialiser l'attribut statique ici. Il faut le faire dans l'espace global, c'est-à-dire en dehors de toute classe ou fonction, en dehors du main() notamment.
1
2
//Initialiser l'attribut en dehors de toute fonction ou classe (espace global)
int MaClasse::monAttribut = 5;

Cette ligne se met généralement dans le fichier .cpp de la classe.


Un attribut déclaré comme statique se comporte comme une variable globale, c'est-à-dire une variable accessible partout dans le code.

Il est très tentant de déclarer des attributs statiques pour pouvoir accéder partout à ces variables sans avoir à les passer en argument de fonctions, par exemple. C'est généralement une mauvaise chose car cela pose de gros problèmes de maintenance. En effet, comme l'attribut est accessible de partout, comment savoir à quel moment il va être modifié ? Imaginez un programme avec des centaines de fichiers dans lequel vous devez chercher l'endroit qui modifie cet attribut ! C'est impossible.
N'utilisez donc des attributs statiques que si vous en avez réellement besoin.


Une des utilisations les plus courantes des attributs statiques est la création d'un compteur d'instances. Il arrive parfois que l'on ait besoin de connaître le nombre d'objets d'une classe donnée qui ont été créés.

Pour y arriver, on crée alors un attribut statique compteur que l'on initialise à zéro. On incrémente ensuite ce compteur dans les constructeurs de la classe et, bien sûr, on le décrémente dans le destructeur. Et comme toujours, il nous faut respecter l'encapsulation (eh oui, on ne veut pas que tout le monde puisse changer le nombre d'objets sans en créer ou en détruire !). Il nous faut donc mettre notre attribut dans la partie privée de la classe et ajouter un accesseur. Cet accesseur est bien sûr une méthode statique !
1
2
3
4
5
6
7
8
9
10
11
class Personnage
{
   public:
   Personnage(string nom);
   //Plein de mthodes
   ~Personnage();
   static int nombreInstances();   //Renvoie le nombre d'objets crs
   private:
   string m_nom;
   static int compteur;
}

Et tout se passe ensuite dans le .cpp correspondant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int Personnage::compteur = 0; //On initialise notre compteur  0
Personnage::Personnage(string nom)
   :m_nom(nom)
{
   ++compteur;  //Quand on cre un personnage, on ajoute 1 au compteur
}
Personnage::~Personnage()
{
   --compteur;  //Et on enlve 1 au compteur lors de la destruction
}
int Personnage::nombreInstances()
{
   return compteur;   //On renvoie simplement la valeur du compteur
}

On peut alors à tout instant connaître le nombre de personnages présents dans le jeu en consultant la valeur de l'attribut Personnage::compteur, c'est-à-dire en appelant la méthode statique nombreInstances().
1
2
3
4
5
6
7
8
9
int main()
{
   //On cre deux personnages
   Personnage goliath("Goliath le tenebreux");
   Personnage lancelot("Lancelot le preux");
   //Et on consulte notre compteur
   cout << "Il y a actuellement " << Personnage::nombreInstances() << " personnages en jeu." << endl;
   return 0;
}

Simple et efficace non ? Vous verrez d'autres exemples d'attributs statiques dans la suite. Ce n'est pas cela qui manque en C++.


L'amitié


Vous savez créer des classes mères, des classes filles, des classes petites-filles, etc. : un vrai arbre généalogique, en quelque sorte. Mais en POO, comme dans la vie, il n'y a pas que la famille, il y a aussi les amis.

Qu'est-ce que l'amitié ?



« Dans les langages orientés objet, l'amitié est le fait de donner un accès complet aux éléments d'une classe. »

Donc si je déclare une fonction f amie de la classe A, la fonction f pourra modifier les attributs de la classe A même si les attributs sont privés ou protégés. La fonction f pourra également utiliser les fonctions privées et protégées de la classe A.

On dit alors que la fonction f est amie de la classe A.

En déclarant une fonction amie d'une classe, on casse complètement l'encapsulation de la classe puisque quelque chose d'extérieur à la classe pourra modifier ce qu'elle contient. Il ne faut donc pas abuser de l'amitié.

Je vous ai expliqué dès le début que l'encapsulation était l'élément le plus important en POO et voilà que je vous présente un moyen de détourner ce concept. Je suis d'accord avec vous, c'est assez paradoxal. Pourtant, utiliser à bon escient l'amitié peut renforcer l'encapsulation. Voyons comment !

Retour sur la classe Duree



Pour vous présenter la surcharge des opérateurs, j'ai utilisé la classe Duree dont le but était de représenter la notion d'intervalle de temps. Voici le prototype de la classe :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Duree
{
   public:
 
   Duree(int heures = 0, int minutes = 0, int secondes = 0);
   void affiche(ostream& out) const;  //Permet d'crire la dure dans un flux
   private:
 
   int m_heures;
   int m_minutes;
   int m_secondes;
};
//Surcharge de l'oprateur << pour l'criture dans les flux
//Utilise la mthode affiche() de Duree
ostream &operator<<( ostream &out, Duree const& duree );

Je ne vous ai mis que l'essentiel. Il y avait bien plus d'opérateurs déclarés à la fin du chapitre. Ce qui va nous intéresser, c'est la surcharge de l'opérateur d'injection dans les flux. Voici ce que nous avions écrit :
1
2
3
4
5
ostream &operator<<( ostream &out, Duree const& duree )
{
   duree.afficher(out) ;
   return out;
}

Et c'est très souvent la meilleure solution ! Mais pas toujours En effet, en faisant cela, vous avez besoin d'écrire une méthode affiche() dans la classe, c'est-à-dire que votre classe va fournir un service supplémentaire.
Vous allez ajouter un levier en plus en surface de votre classe (figure suivante).


Sauf que ce levier n'est destiné qu'à l'opérateur << et pas au reste du monde. Il y a donc une méthode dans la classe qui, d'une certaine manière, ne sert à rien pour un utilisateur normal.
Dans ce cas, cela ne porte pas vraiment à conséquence. Si quelqu'un utilise la méthode affiche(), alors rien de dangereux pour l'objet ne se passe. Mais dans d'autres cas, il pourrait être risqué d'avoir une méthode qu'il ne faut surtout pas utiliser.
C'est comme dans les laboratoires, si vous avez un gros bouton rouge avec un écriteau indiquant « Ne surtout pas appuyer », vous pouvez être sûrs que quelqu'un va, un jour, faire l'erreur d'appuyer dessus.
Le mieux serait donc de ne pas laisser apparaître ce levier en surface de notre cube-objet. Ce qui revient à mettre la méthode affiche() dans la partie privée de la classe.
1
2
3
4
5
6
7
8
9
10
11
12
class Duree
{
   public:
 
   Duree(int heures = 0, int minutes = 0, int secondes = 0);
   private:
   void affiche(ostream& out) const;  //Permet d'crire la dure dans un flux  
 
   int m_heures;
   int m_minutes;
   int m_secondes;
};

En faisant cela, plus de risque d'appeler la méthode par erreur. Par contre, l'opérateur << ne peut plus, lui non plus, l'utiliser.
C'est là que l'amitié intervient. Si l'opérateur << est déclaré ami de la classe Duree, il aura accès à la partie privée de la classe et, par conséquent, à la méthode affiche().

Déclarer une fonction amie d'une classe



Interro surprise d'anglais. Comment dit-on « ami » en anglais ?


« Friend », exactement ! Et comme les créateurs du C++ ne voulaient pas se casser la tête avec les noms compliqués, ils ont pris comme mot-clé friend pour l'amitié. D'ailleurs si vous tapez ce mot dans votre IDE, il devrait s'écrire d'une couleur différente.

Pour déclarer une fonction amie d'une classe, on utilise la syntaxe suivante :
1
friend std::ostream& operator<< (std::ostream& flux, Duree const& duree);

On écrit friend suivi du prototype de la fonction et on place le tout à l'intérieur de la classe :
1
2
3
4
5
6
7
8
9
10
11
12
13
class Duree
{
   public:
 
   Duree(int heures = 0, int minutes = 0, int secondes = 0);
   private:
   void affiche(ostream& out) const;  //Permet d'crire la dure dans un flux
 
   int m_heures;
   int m_minutes;
   int m_secondes;
   friend std::ostream& operator<< (std::ostream& flux, Duree const& duree);
};

Vous pouvez mettre le prototype de la fonction dans la partie publique, protégée ou privée de la classe, cela n'a aucune importance.


Notre opérateur << a maintenant accès à tout ce qui se trouve dans la classe Duree, sans aucune restriction. Il peut donc en particulier utiliser la méthode affiche(), comme précédemment, sauf que désormais, c'est le seul élément hors de la classe qui peut utiliser cette méthode.

On peut utiliser la même astuce pour les opérateurs == et <. En les déclarant comme amies de la classe Duree, ces fonctions pourront accéder directement aux attributs et l'on peut alors supprimer les méthodes estPlusPetitQue() et estEgal(). Je vous laisse essayer...

L'amitié et la responsabilité



Être l'ami de quelqu'un a certaines conséquences en matière de savoir-vivre. Je présume que vous n'allez pas chez vos amis à 3h du matin pour saccager leur jardin pendant leur sommeil.

En C++, l'amitié implique également que la fonction amie ne viendra pas détruire la classe ni saccager ses attributs. Si vous avez besoin d'une fonction qui doit modifier grandement le contenu d'une classe, alors faites plutôt une fonction membre de la classe.

Vos programmes devraient respecter les deux règles suivantes :
  • une fonction amie ne doit pas, en principe, modifier l'instance de la classe ;

  • les fonctions amies ne doivent être utilisées que si vous ne pouvez pas faire autrement.

Cette deuxième règle est très importante. Si vous ne la respectez pas, alors autant arrêter la POO car le concept de classe perd tout son sens.




En résumé



  • Une méthode statique est une méthode qui peut être appelée directement sans créer d'objet. Il s'agit en fait d'une fonction classique.

  • Un attribut statique est partagé par tous les objets issus d'une même classe.

  • Une fonction amie d'une classe peut accéder à tous ses éléments, même les éléments privés.

  • L'amitié doit être utilisée avec parcimonie en C++, uniquement lorsque cela est nécessaire.




Chapitre précédent     Sommaire



Distribué et adapté par David
Consulté 2323 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 240 jours 12 heures 32 minutes
Espace libre : 1569 Mo
Dernière sauvegarde : 22/07/2019
Taille de la sauvegarde : 1112 Mo


5357246 pages ont été consultées sur le site !
Dont 2696 pages pendant les 24 dernières heures.

Page générée en 0.551 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2019 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.