• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 25/03/2019

Utilisez les éléments statiques et l'amitié

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

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…

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éez une méthode statique

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

class MaClasse
{
    public:
    MaClasse();
    static void maMethode();
};

Son implémentation dans le.cppne possède pas en revanche de mot-cléstatic:

void MaClasse::maMethode() //Ne pas remettre 'static' dans l'implémentation
{
    cout << "Bonjour !" << endl;
}

Ensuite, dans lemain(), la méthode statique s'appelle comme ceci :

int main()
{
    MaClasse::maMethode();
 
    return 0;
}

Mais… on n'a pas créé d'objet de type MaClasseet 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 QDatequi 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éez un attribut statique dans une classe

C'est assez simple en fait : il suffit de rajouter le mot-clé staticau 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 :

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.

//Initialiser l'attribut en dehors de toute fonction ou classe (espace global)
int MaClasse::monAttribut = 5;

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

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 compteurque 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 !

class Personnage
{
    public:
    Personnage(string nom);
    //Plein de méthodes…
    ~Personnage();
    static int nombreInstances();   //Renvoie le nombre d'objets créés

    private:
    string m_nom;
    static int compteur;
}

Et tout se passe ensuite dans le .cppcorrespondant :

int Personnage::compteur = 0; //On initialise notre compteur à 0

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 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'attributPersonnage::compteur, c'est-à-dire en appelant la méthode statiquenombreInstances().

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 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 famie de la classeA, la fonction fpourra modifier les attributs de la classe Amême si les attributs sont privés ou protégés. La fonction fpourra également utiliser les fonctions privées et protégées de la classeA.

On dit alors que la fonction fest amie de la classeA.

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 Dureedont le but était de représenter la notion d'intervalle de temps. Voici le prototype de la classe :

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 :

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 :

Notre classe fournit un service supplémentaire d'affichage
Notre classe fournit un service supplémentaire d'affichage

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éthodeaffiche()dans la partie privée de la classe.

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 classeDuree, 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é friendpour 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 :

friend std::ostream& operator<< (std::ostream& flux, Duree const& duree);

On écrit friendsuivi du prototype de la fonction et on place le tout à l'intérieur de la classe :

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);
};

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.

Exemple de certificat de réussite
Exemple de certificat de réussite