Aller au menu - Aller au contenu

Icône Eléments statiques et amitié

Mise à jour : 27/05/2011
Difficulté : Facile Facile Durée d'étude : 1 jour Creative Commons BY-NC-SA
75 639 visites depuis 7 jours, dont 926 sur ce chapitre classé 5/786
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 et 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 ça juste pour le plaisir de programmer de façon tordue :p
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Les méthodes statiques

Ah les méthodes statiques... Alors ça, c'est un peu spécial ^^
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. Ca s'utilise d'une manière un peu particulière.

Le mieux est encore un exemple je pense !


Créer une méthode statique



Dans le .h, le prototype d'une méthode statique ressemble à ceci :

Code : C++
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 :

Code : C++
1
2
3
4
void MaClasse::maMethode() // Ne pas remettre "static" dans l'implémentation
{
    cout << "Bonjour !" << endl;
}



Ensuite, dans le main, la méthode statique s'appelle comme ceci :

Code : C++
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 ça la particularité des méthodes statiques. Pour les utiliser, pas besoin de créer un objet. Il suffit juste de faire précéder le nom de la méthode par le nom de la classe suivi de deux 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. Ça 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, mais 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 ça 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 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, ça n'aurait pas eu d'intérêt d'instancier un objet à partir de la classe car ce sont des informations générales.
  • etc etc.

Mmmh mais c'est que ça donne envie de travailler avec Qt tout ça ^^

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 ça une exception, car c'en est bien une. ^^

Exemple :

Code : C++
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.

Code : C++
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. Cela pose des gros problèmes de maintenance. En effet, comme l'attribut est accessible depuis 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. :pirate:


Une des utilisations les plus courantes des attributs statiques est la création d'un compteur d'instance. Il arrive parfois que l'on ait besoin de connaître combien d'objets d'une certaine classe 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 ! ;)

Code : C++ - Un compteur d'instance
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Personnage
{
    public:
    Personnage(string nom);
    // Plein de méthodes...
    ~Personnage();

    static int nombreInstances();

    private:
    string m_nom;
    static int compteur;
}


Et tout se passe ensuite dans le .cpp correspondant.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int Personnage::compteur = 0; //On initialise à 0 notre compteur

Personnage::Personnage(string nom)
    :m_nom(nom)
{
    ++compteur;     //Quand on crée un personnage, on ajoute 1 au compteur;
}

Personnage::~Personnage()
{
    --compteur;    //Et on enlève 1 au compteur lors de la destruction
}

int Personnage::nombreInstances()
{
    return 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 nombreInstances().

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
    //On crée 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 dans la suite d'autres exemples d'attributs statiques. Ce n'est pas ça 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é ?



L'amitié dans les langages orientés objet 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 puisqu'un être externe à la classe pourra modifier ce qu'il y a dedans. Il ne faut donc pas abuser de l'amitié. :pirate:

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 :

Code : C++ - Prototype de la classe Duree
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Duree
{
    public:
 
    Duree(int heures = 0, int minutes = 0, int secondes = 0);
    void affiche(ostream& out) const;   //Permet d'écrire la durée dans un flux 

    private:
 
    int m_heures;
    int m_minutes;
    int m_secondes;
};

//Surcharge de l'opérateur << pour l'écriture dans les flux
//Utilise la méthode 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 :

Code : C++ - Surcharge de l'opérateur <<
1
2
3
4
5
ostream &operator<<( ostream &out, Duree const& duree )
{
    duree.affiche(out) ;
    return out;
}


Et c'est très souvent la meilleure solution ! Mais pas toujours... En effet, en faisant ça, 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.

Image utilisateur


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. Il s'écrit juste dans la console ou dans un fichier. Mais dans d'autres cas, il pourrait être dangereux 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ûr que quelqu'un va, un jour, faire l'erreur d'appuyer dessus. :p
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.

Code : C++ - Prototype de la classe Duree
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Duree
{
    public:
 
    Duree(int heures = 0, int minutes = 0, int secondes = 0);

    private:

    void affiche(ostream& out) const;   //Permet d'écrire la durée 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 friend comme mot-clé pour l'amitié. D'ailleurs si vous tapez ce mot dans votre IDE, il devrait s'écrire d'une couleur différente. C'est bien la preuve. ^^

Pour déclarer une fonction amie d'une classe, on utilise la syntaxe suivante :

Code : C++
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.


Code : C++ - Prototype de la classe Duree
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Duree
{
    public:
 
    Duree(int heures = 0, int minutes = 0, int secondes = 0);

    private:

    void affiche(ostream& out) const;   //Permet d'écrire la durée 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 amis 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 et ne viendra pas non plus saccager les attributs de la classe. 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 devrait, en principe, pas modifier l'instance de la classe.
  • Utilisez les fonctions amies 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.

Q.C.M.

Un attribut statique se comporte comme...
Comment fait-on appel à une méthode statique ?
A quels constituants d'une classe, une fonction amie a-t-elle accès ?

Statistiques de réponses au QCM

Après avoir emmagasiné toutes ces connaissances, nous allons pratiquer un peu avec la bibliothèque Qt et créer des fenêtres ! :)
Chapitre précédent Sommaire Chapitre suivant

Partager

31 commentaires pour "Eléments statiques et amitié"
Note moyenne : 3.85 / 4 (1749 votes)
Pseudo Commentaire
En ligne Nanoc # Posté le 23/08/2011 à 09:31:35
Aimez-vous le C++ ?
Avatar
Validateurs

Ville : Durham
Pays : Royaume-Uni
Études : EPFL

Je suis d'accord, c'est un raccourci facile. Mais pour débuter, il est bon d'insister sur le fait qu'il ne faut pas abuser de l'amitié puisqu'elle permet d'accéder à toute la classe. Je sais d'expérience que pas mal de débutants abusent de l'amitié dans leur codes pour ne pas avoir à réfléchir de manière correcte à ce qui devrait être dans l'interface de la classe.

Citation : Marc Mongenet

Les fonctions amies font partie de l'interface et peuvent manipuler les membres privés comme toute méthode. Passer par une méthode privée comme affiche est du verbiage inutile.


Par contre, je me porte en faux contre cette remarque.
En utilisant une méthode affiche() privée, je peux utiliser le polymorphisme au niveau de mon opérateur <<. Je déclare une seule fois l'opérateur << et il appellera la méthode affiche() correcte selon la vraie nature de l'objet reçu sous réserve bien sûr que affiche() soit virtuelle. C'est tout l'intérêt de cette manière de faire.
 
Hors ligne saidox # Posté le 30/08/2011 à 03:52:53

merci pour ce Tutorial.
je veut connaitre combien de fois j'ai instancié de la classe véhicule alors je vais utiliser un attribue statice qui sera incrémenter à chaque fois que j’appelle le constructeur de véhicule,c'est bon mais si je créer un objet de type voiture sachant que la classe voiture hérite de la classe véhicule le constructeur de la classe véhicule va être appeler par le constructeur de la classe voiture et comme ça notre compteur va être incrémenter seulement en instancions de la classe voiture et pas de la classe véhicule,et ce n'est pas ça ce que je veut alors comment faire merci d'avance.
Hors ligne Marc Mongenet # Posté le 02/09/2011 à 12:08:09

Citation : Nanoc
Je suis d'accord, c'est un raccourci facile. Mais pour débuter, il est bon d'insister sur le fait qu'il ne faut pas abuser de l'amitié puisqu'elle permet d'accéder à toute la classe. Je sais d'expérience que pas mal de débutants abusent de l'amitié dans leur codes pour ne pas avoir à réfléchir de manière correcte à ce qui devrait être dans l'interface de la classe.

Oui, je comprends qu'on mette vivement en garde les débutants, face à cette syntaxe de facilité, pour qui vient de la programmation non objet.

Mais prétendre que l'encapsulation est cassée reste une grossière erreur, une incompréhension fondamentale de l'amitié et l'encapsulation. Et d'expérience, je sais que les débutants restent très longtemps sur cette idée fausse, et l'on trouve même des professeurs pour propager cette erreur (et des tutoriels)!

Citation : Nanoc
En utilisant une méthode affiche() privée, je peux utiliser le polymorphisme au niveau de mon opérateur <<. Je déclare une seule fois l'opérateur << et il appellera la méthode affiche() correcte selon la vraie nature de l'objet reçu sous réserve bien sûr que affiche() soit virtuelle. C'est tout l'intérêt de cette manière de faire.

Ah oui, très juste, pour bénéficier du polymorphisme, il faut une méthode.
Après, la classe n'a pas de destructeur virtuelle, il n'est donc pas question d'en dériver, non?
Connecté Nanoc # Posté le 02/09/2011 à 13:24:12
Aimez-vous le C++ ?
Avatar
Validateurs

Ville : Durham
Pays : Royaume-Uni
Études : EPFL

Non, celle-là n'a pas vocation à être dérivée. Mais d'autres peuvent l'être dans le code des gens et je préfère donc donner la syntaxe "la meilleure" pour que les gens s'y habituent.

Je vais réfléchir au fait d'ajouter une remarque concernant l'aspect avancé qui veut que l'encapsulation n'est en faite pas cassée. Maintenant j'espère que les profs qui enseignent ont d'autres sources que ce petit cours... ;)
 
Hors ligne Aerien9z # Posté le 02/10/2011 à 16:26:48
Avatar

Avis : Très bon

Études : IUT Dijon

Citation
en POO, comme dans la vie, il n'y a pas que la famille, il y a aussi les amis.


:D

Pfiou enfin arrivé à la partie pratique de ce cours ! Ces 20 chapitres théoriques nous auront bien fait souffrir... Mais vraiment, des notions super bien expliqué, un cours clair, illustré avec de nombreux exemples ! Merci beaucoup pour tout ce que j'ai appris :)

Maintenant, à moi Qt !! :p

Voir tous les commentaires