Dans ce chapitre, vous allez découvrir quelques notions spécifiques aux classes en C++ :
Les méthodes statiques.
Les attributs statiques.
L'amitié.
Créez une méthode statique
Elles s'utilisent d'une manière un peu particulière. Je pense que le mieux est encore un exemple.
Dans le .hpp
, le prototype d'une méthode statique ressemble à ceci :
class MaClasse
{
public:
MaClasse();
static void maMethode();
};
Son implémentation dans le .cpp
ne 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 le main()
, la méthode statique s'appelle comme ceci :
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 ?
Pourquoi avoir inventé le modèle objet si c'est pour autoriser les gens à créer des fonctions regroupées dans des classes ?
La réponse, c'est qu'on a toujours besoin d'utiliser des 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.
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
Pour cela, il suffit de rajouter le mot-clé static
au début de la ligne.
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.
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.
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 (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 .cpp
correspondant :
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'attribut Personnage::compteur
, c'est-à-dire en appelant la méthode statique nombreInstances()
.
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;
}
Voyons tout de suite dans le screencast suivant comment créer une classe contenant un attribut et une méthode statique, comme dans l’exemple au-dessus :
Simple et efficace, non ?
Découvrez le principe d'amitié
Bon ! 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é ?
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
.
Je vous ai expliqué 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 !
Revenez 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 :
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 (ou un nouveau bouton sur votre cube-objet opaque, si vous préférez). 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 pas appuyer", vous pouvez être sûr 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 :
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éclarez une fonction amie d'une classe
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é.
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 friend
suivi 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 étant déclarées 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()
.
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 c'est nécessaire.
Vous avez appris plein de choses dans cette première partie. Il est maintenant temps de tester vos connaissances à l’aide d’un petit quiz. Bonne chance !