Big Tuto : Apprenez le C++

Chapitre 16 : Introduction aux classes (2)

 

Tutoriel présenté par : Robert Gillard (Gondulzac)
Date de publication : 29 octobre 2016
Date de révision : 3 janvier 2017

 

Retrouvez les projets complets de ce chapitre :

  

 

 

   1 – Utilisation d'une liste d'initialisation dans l'implémentation d'un constructeur ou d'une fonction membre

 

    Jusqu'à présent, nous avons initialisé les variables membres d'un constructeur ou d'une méthode dans le corps du constructeur ou de la méthode. En effet, Si nous nous rappelons du projet 092Class_3 du chapitre 15, nous écrivions les implémentations du constructeur par défaut, des constructeurs surchargés ainsi que des fonctions membres d'accès publique du fichier 092Class_3.cpp dans le corps des constructeurs ou des fonctions, soit :

 

//Implémentation du constructeur par défaut
Player::Player()
{
_nom = "Aria";
_race = "Humain";
_sexe = "Feminin";
}
 
//Implémentation du 1er constructeur surchargé
Player::Player(string name)
{
_nom = name;
}
 
//Implémentation du 2eme constructeur surchargé
Player::Player(string name, string race)
{
_nom = name;
_race = race;
}
 
//Implémentation du 3eme constructeur surchargé
Player::Player(string name, string race, string sexe)
{
_nom = name;
_race = race;
_sexe = sexe;
}
 
//Implémentation du destructeur
Player::~Player() {}
 
//Implémentation des fonction membres d'accès publique
string Player::get_Name() { return _nom; }
 
string Player::get_Race() { return _race; }
 
string Player::get_Sexe() { return _sexe; } 

 

   Au niveau des phases d'initialisation, le C++ est relativement permissif. Nous pouvons modifier celles-ci de deux manières différentes :

 

 

      1.1 – Dans le fichier d'implémentation

 

   Soit garder le fichier d'implémentation ci-dessus mais en définissant nos variables selon ce qu'on appelle des listes d'initialisations telles que représentées ci-dessous :

 

   Exemple 1 :

 

//Implémentation du constructeur par défaut
Player::Player() : _nom("Aria"), _race("Humain"), _sexe("Feminin") {}
 
//Implémentation du 1er constructeur surchargé
Player::Player(string name) : _nom(name) {}
 
//Implémentation du 2eme constructeur surchargé
Player::Player(string name, string race) : _nom(name), _race(race) {}
 
//Implémentation du 3eme constructeur surchargé
Player::Player(string name, string race, string sexe) : _nom(name), _race(race), _sexe(sexe{}
 
//Implémentation du destructeur
Player::~Player() {}
 
//Implémentation des fonction membres d'accès publique
string Player::get_Name() { return _nom; }
 
string Player::get_Race() { return _race; }
 
string Player::get_Sexe() { return _sexe; } 

 

   Pour ce qui est des fonctions membres d'accès publique, celles-ci peuvent également être déclarées ''inline'' dans le fichier d'implémentation .cpp. Il suffit dès lors de faire précéder le type de la fonction par le mot-clé ''inline'', soit :

 

inline string Player::get_Name() { return _nom; }
 
inline string Player::get_Race() { return _race; }
 
inline string Player::get_Sexe() { return _sexe; } 

 

   Mais il serait préférable, comme nous allons le voir dans le paragraphe suivant, d'implémenter de courtes fonctions directement dans le fichier de définition (Voir chapitre 14 - § 1.4, Implémentation d'une fonction inline dans une structure).

   Dans ce cas le mot-réservé ''inline'' n'est plus nécessaire. wink

 


      1.2 – Dans le fichier de déclaration (Implémentation de méthodes ''inline'')


   Soit initialiser directement nos variables et nos méthodes en ligne dans le fichier de déclaration. Dans ce cas, nous n'avons plus besoin de fichier d'implémentation dans le projet au risque d'avoir un fichier d'initialisation plus ou moins chargé.

   Afin de bien nous rendre compte de cette différence, nous allons réécrire ici le fichier 096Class_3.h en utilisant directement les listes d'initialisation dans le fichier header et en y ajoutant l'implémentation de nos méthodes ''inline''.

 

   Exemple 2 :  Fichier 096Class_3.h

 

//projet 096Class_3
//Fichier : 096Class_3.h
//Déclaration de la classe Player
//Constructeurs surchargés
#ifndef DEF_096CLASS_3
#define DEF_096CLASS_3
 
#include <iostream>
#include <string>
 
class Player
{
public:
 
//Constructeur par défaut
Player() : _nom("Aria"), _race("Humain"), _sexe("Feminin") {}
 
//Constructeurs surchargés
Player(std::string name) : _nom(name) {}
Player(std::string name, std::string race) : _nom(name), _race(race) {}
Player(std::string name, std::string race, std::string sexe) :
_nom(name), _race(race), _sexe(sexe) {}
 
//Destructeur
~Player() {};
 
//Fonctions membres d'accès publique
//Méthodes en ligne
std::string get_Name() const { return _nom; }
std::string get_Race() const { return _race; }
std::string get_Sexe() const { return _sexe; }
 
private:
//Données membres privées
std::string _nom;
std::string _race;
std::string _sexe;
 
};
 
#endif 

 

   Et voilà, remplacez le fichier header de ce projet du chapitre 14 par celui-ci, supprimez le fichier 096Class_3.cpp et recompilez le tout. Le résultat sera le même que celui du projet du chapitre 14. wink

   Nous avons donc différentes formes d' initialisations des données membres (avec ou sans liste d'initialisation) ainsi que l'implémentation des méthodes en ligne ou non. Cette dernière forme, qui nous permet de gagner la manipulation d'un fichier, pourra se révéler utile par la suite mais d'autre part très vite se complexifier lors de programmes bien plus importants et notamment lors de saisies clavier. Vous devriez cependant vous familiariser avec ces différentes formes d'initialisations. Vous verrez par la lecture de livres ou de tutoriels C++ sur le net ces différentes formes utilisées par de nombreux auteurs. Nous tâcherons de vous y familiariser un maximum dans ce big tuto. angel

 

      Exercice 1 :

   Réécrivez le projet 095Class_2 du chapitre 15 en initialisant un constructeur sous la forme de liste d'initialisations ainsi que les fonctions membres dans le fichier 095Class_2.h.

 


   2 – Surcharge de méthodes

 

   Dans le chapitre 6 - § 2.3, nous avons expliqué en quoi consistait la surcharge de fonctions en C++. Avec l'introduction des classes, de même que l'on peut surcharger un constructeur par défaut (chapitre 15 - § 7.2), nous allons maintenant voir comment surcharger des méthodes ou fonctions membres. angel

   Pour ce faire, nous prendrons un nouvel exemple, représentant un petit stock inventaire marchand. wink

 

   Exemple 3 : Projet 097Class_4 :

 

   Et par un bienheureux hasard nous retrouvons notre marchand Ethiolas en pleine cohue d'un marché au sud de la Vallée de Perdagne. Celui-ci nous montre sa panoplie de boucliers et nous assure qu'un objet de défense ne serait pas inutile dans ces environnements peu sûrs.

   Afin de ne pas trop charger notre exemple nous ne créerons qu'un seul objet ''bouclier'' dans la fonction main(). Nous initialiserons cependant les données et cinq méthodes surchargées Lire_Arme() dans un fichier .cpp séparé. L'utilisation de ces méthodes se fera dans la fonction main().

 

   Fichier 097Class_4.h  

 

//projet 097Class_4
 
//Fichier : 097Class_4.h
//Déclaration de la classe Bouclier
//Méthodes surchargées
#ifndef DEF_097CLASS_4
#define DEF_097CLASS_4
 
#include <iostream>
#include <string>
 
using uint = unsigned int;
 
class Bouclier
{
public:
 
//Constructeur par défaut
Bouclier(std::string nom, std::string classe, uint prix_Achat, uint defense, uint resistance );
 
//Destructeur
~Bouclier();
 
//Méthode Lire_Arme()
void Lire_Arme(const;
 
//Méthodes Lire_Arme() surchargées
void Lire_Arme(std::string nom) const;
void Lire_Arme(std::string nom, std::string classe) const;
void Lire_Arme(std::string nom, std::string classe, uint prix_Achat) const;
void Lire_Arme(std::string nom, uint prix_Achat) const;
void Lire_Arme(std::string nom, uint defense, uint resistance) const;
void Lire_Arme(std::string nom, std::string classe, uint prix_Achat, uint defense, uint resistance) const;
 
private:
//Données membres privées
std::string _name;
std::string _class;
uint _PA;
uint _defense;
uint _resistance;
 
};
#endif 

 

   Dans ce fichier, nous définissons en premier un constructeur par défaut comportant cinq paramètres.

 

Attention : un constructeur par défaut n'est pas qu'un constructeur sans paramètres à l'intérieur de ses parenthèses. Vous pouvez créer un constructeur par défaut comportant un nombre quelconque de paramètres et c'est ce qui fait la différence entre un constructeur par défaut créé par le compilateur et le vôtre créé selon vos propres besoins !

 

   La variable ''classe'' du constructeur pourrait représenter un bouclier en bois, une targe ou tout autre bouclier renforcé. La variable ''defense'' représente par exemple un bonus de défense octroyé au héros et la variable ''resistance'', comme son nom l'indique représente la résistance du bouclier supposé neuf. cheeky

   Suivent le destructeur et quelques méthodes surchargées, toutes dénommées ''Lire_Arme'' et qui afficheront les paramètres passés à l'intérieur de leurs parenthèses.

 

   Fichier 097Class_4.cpp  

 

//projet 097Class_4
//Fichier : 097Class_4.cpp
//Implémentation du constructeur par défaut et des méthodes surchargées
 
#include "097Class_4.h"
#include <string>
 
using namespace std;
 
//Implémentation du constructeur par défaut
Bouclier::Bouclier(string nom, string classe, uint prix_Achat, uint defense, uint resistance)
{
_name = nom;
_class = classe;
_PA = prix_Achat;
_defense = defense;
_resistance = resistance;
}
 
//Destructeur
Bouclier::~Bouclier() {}
 
//Implémentation de la méthode Lire_Arme()
void Bouclier::Lire_Arme() const
{
Lire_Arme(_name, _class, _PA, _defense, _resistance);
}
 
//IMPLEMENTATION DES METHODES SURCHARGEES
//Implémentation de la méthode prenant 5 paramètres
void Bouclier::Lire_Arme(string nom, string classe, uint prix_Achat, uint defense, uint resistance) const
{
cout << "Nom : " << nom << endl;
cout << "Classe : " << classe << endl;
cout << "Prix achat : " << prix_Achat << endl;
cout << "Defense : " << defense << endl;
cout << "Resistance : " << resistance << endl;
}
 
//Implémentation de la méthode surchargée prenant le nom en paramètre
void Bouclier::Lire_Arme(string nom) const
{
cout << "Nom : " << nom << endl;
}
 
//Implementation de la méthode surchargée prenant le nom et la classe en paramètres
void Bouclier::Lire_Arme(string nom, string classe) const
{
cout << "Nom : " << nom << endl;
cout << "Classe : " << classe << endl;
}
 
//Implementation de la méthode surchargée prenant le nom, la classe
//et le prix d'achat en paramètres
void Bouclier::Lire_Arme(string nom, string classe, uint prix_Achat) const
{
cout << "Nom : " << nom << endl;
cout << "Classe : " << classe << endl;
cout << "Prix d'achat : " << prix_Achat << endl;
}
 
//Implementation de la méthode surchargée prenant le nom et le prix d'achat en paramètres
void Bouclier::Lire_Arme(string nom, uint prix_Achat) const
{
cout << "Nom : " << nom << endl;
cout << "Prix d'achat : " << prix_Achat << endl;
}
 
//Implementation de la méthode surchargée prenant le nom, la défense
//et la résistance en paramètres
void Bouclier::Lire_Arme(string nom, uint defense, uint resistance) const
{
cout << "Nom : " << nom << endl;
cout << "Defense : " << defense << endl;
cout << "Resistance : " << resistance << endl;
} 

 

   Dans ce fichier, nous implémentons le constructeur par défaut ainsi que les différentes méthodes surchargées.

   Remarquez la forme d'implémentation de la méthode Lire_Arme() sans paramètres. Cette méthode se chargera d'appeler une version de la fonction avec les variables membres qui lui seront réellement passées et ici il s'agit, bien entendu, des variables d'accès privé de la classe. wink


   Fichier 097_4mainFile.cpp  

 

//projet 097Class_4
//Fichier : 097_4mainFile.cpp
//Utilisation de méthodes surchargées
 
#include "097Class_4.h"
#include <string>
#include <conio.h>
 
using namespace std;
 
int main()
{
string nom, classe;
uint prix_Achat(0), defense(0), resistance(0);
 
//Création d'un objet de type Bouclier
Bouclier unBouclier("Bouclier du Preux Chevalier", "Grand Pavois", 500, 6, 2000);
 
//Utilisation de la méthode prenant les cinq paramètres
cout << endl;
cout << "Fonction Lire_Arme() :" << endl;
unBouclier.Lire_Arme();
cout << endl;
 
//Utilisation de la méthode surchargée prenant le nom en paramètre
cout << "Fonction Lire_Arme(nom) :" << endl;
unBouclier.Lire_Arme("Bouclier du Fantassin");
cout << endl;
 
//Utilisation de la méthode surchargée prenant le nom
//et la classe en paramètres
cout << "Fonction Lire_Arme(nom, classe) :" << endl;
unBouclier.Lire_Arme("Bouclier du Heros", "Pavois");
cout << endl;
 
//Utilisation de la méthode surchargée prenant le nom,
//la classe et le prix d'achat en paramètres
cout << "Fonction Lire_Arme(nom, classe, prix_Achat) :" << endl;
unBouclier.Lire_Arme("Bouclier du Heros", "Pavois", 400);
 
cout << endl;
//Utilisation de la méthode surchargée prenant le nom
//et le prix d'achat en paramètres
cout << "Fonction Lire_Arme(nom, prix_Achat) :" << endl;
unBouclier.Lire_Arme("Bouclier du Nain de Thorgul", 200);
cout << endl;
 
//Utilisation de la méthode surchargée prenant le nom,
//la defense et la resistance en paramètres
cout << "Fonction Lire_Arme(nom, defense, resistance) :" << endl;
unBouclier.Lire_Arme("Bouclier d'Ethiolas", 2, 100);
 
_getch();
return 0;
} 

 

   Au début de la fonction main() nous déclarons 5 variables qui seront utilisées selon les paramètres passés aux fonctions surchargées.

   On crée ensuite un objet de type Bouclier à qui l'on passe les 5 paramètres du constructeur. La fonction main() appelle par la suite 5 formes surchargées de la méthode Lire_Arme() avec les variables passées en paramètres lors de l'implémentation des méthodes dans le fichier 097Class_4.cpp.

   Et voilà le résultat dans la console :

 

 

      Exercice 2 :

   Réécrivez le fichier 097Class_4.h en utilisant une liste d'initialisation et en implémentant les définitions des méthodes membres dans ce même fichier.

 


   3 – Déclaration de variables membres d'une classe et création d'objets dans le tas

 

      3.1 – Création d'objets dans le tas

 

   Nous l'avons vu lors de l'étude de fonctions membres dans les structures (Chapitre 14 - Projet 089Struct_7), nous pouvons créer un objet dans le tas en renvoyant un pointeur ou une référence sur cet objet et il en va bien entendu de même en ce qui concerne les classes. En effet, dans le fichier 097_4MainFile.cpp, nous avons créé un objet unBouclier sur la pile :

Bouclier unBouclier("Bouclier du Preux Chevalier", "Grand Pavois", 500, 6, 2000);

 

   Ceci nous amène à présenter deux nouveaux exemples dans lesquels nous n'écrirons ici que les modifications des fichiers ''mainFile.cpp'', vous pourrez télécharger les projets complets en annexe. wink

 

 


   Exemple 4 : Projet 098Class_5

      Fichier 098_5mainFile.cpp

 

   Nous aurions pu créer l'objet ''unBouclier'' dans le tas avec new renvoyant un pointeur sur l'objet, et dans ce cas nous aurions écrit :

Bouclier *unBouclier = new Bouclier("Bouclier du Preux Chevalier", "Grand Pavois", 500, 6, 2000);

 

   La lecture du fichier Lire_Arme() devant alors se faire à l'aide de l'opérateur ''flèche'' (voir chapitre 14 – Projet 089Struct_7), soit :

 

unBouclier->Lire_Arme(paramètres...);

 

   Et bien entendu, libérer la mémoire à la fin de la fonction main() avec l'instruction :

 

delete unBouclier;


   Une seconde possibilité est de créer l'objet ''unBouclier'' dans le tas avec new renvoyant une référence sur l'objet.

 

 

   Exemple 5 : Projet 099Class_6

      Fichier 099_6mainFile.cpp

 

   Soit :

Bouclier &unBouclier = *new Bouclier("Bouclier du Preux Chevalier", "Grand Pavois", 500, 6, 2000);

 

   La lecture du fichier Lire_Arme() se fait dans ce cas à l'aide de l'opérateur ''point'' (voir fichier 097_4mainFile.cpp), soit :

 

unBouclier.Lire_Arme(paramètres...);

 

   Et libérer la mémoire à la fin de la fonction main() avec l'instruction :

 

delete &unBouclier;

 


      3.2 – Création de variables membres dans le tas

 

   Les variables membres d'une classe, appelées également ''attributs'' peuvent aussi être déclarées dans le tas, comme nous l'avons vu dans le chapitre 14 concernant les structures et nous en verrons toute l'utilité dans le paragraphe 4 concernant le constructeur de copie.

   En attendant, nous allons créer un nouveau projet, sur l'exemple du projet 096Class_3 mais sans utiliser de constructeur surchargé cette fois. wink

 

   Exemple 6 : Projet 100Class_7

      Fichier 100Class_7.h  

 

//projet 100Class_7
//Fichier : 100Class_7.h
//Déclaration de la classe Player
//Réservation des variables membres dans le tas
#ifndef DEF_100CLASS_7
#define DEF_100CLASS_7
 
#include <iostream>
#include <string>
 
class Player
{
public:
//Constructeur par défaut
Player();
 
//Destructeur
~Player();
 
//Fonctions membres d'accès publique
//Acquisition de données
std::string get_Name() const;
std::string get_Race() const;
std::string get_Sexe() const;
 
private:
//Données membres privées
std::string *_nom;
std::string *_race;
std::string *_sexe;
 
};
#endif 

 

   Dans ce fichier nous déclarons un constructeur par défaut, un destructeur, ainsi que trois fonctions membres d'accès publique. Les données membres (attributs) de type string sont quant-à-eux déclarés comme pointeurs.

 

      Fichier 100Class_7.cpp

 

//projet 100Class_7
//Fichier : 100Class_7.cpp
//Implémentation de la classe Player
//Réservation des variables membres dans le tas
 
#include "100Class_7.h"
#include <string>
 
using namespace std;
 
//Implémentation du constructeur par défaut
Player::Player()
{
_nom = new string { "Aria" };
_race = new string { "Humain" };
_sexe = new string { "Feminin" };
}
 
//Implémentation du destructeur
Player::~Player()
{
delete _nom;
*_nom = nullptr;
delete _race;
*_race = nullptr;
delete _sexe;
*_sexe = nullptr;
}
 
//Implémentation des fonctions membres d'accès publique
string Player::get_Name() const { return *_nom; }
 
string Player::get_Race() const { return *_race; }
 
string Player::get_Sexe() const { return *_sexe; } 

 

   Les variables membres ayant été déclarées pointeurs, nous réservons la mémoire dans le tas pour ces trois attributs strings en même temps que l'on procède à leur initialisation.

   Le destructeur libère la mémoire réservée pour nos trois variables et si nous voulons suivre les recommandations du standard C++ 11, nous faisons pointer nos pointeurs sur nullptr. Nous terminons avec l'implémentation de trois fonctions membres d'accès publique, chacune retournant la variable représentative de chaque méthode.

 

   Fichier 100Class_7mainFile.cpp

 

//projet 100Class_7
//Fichier : 100Class_7mainFile.cpp
//Réservation des variables membres dans le tas
 
#include "100Class_7.h"
#include <string>
#include <conio.h>
 
using namespace std;
 
int main()
{
//Création d'un objet de type Player
Player heros;
 
//On affiche les membres de l'objet
cout << endl;
cout << "Reservation de variables membres dans le tas :" << endl << endl;
cout << "Nom : " << heros.get_Name() << endl;
cout << "Race : " << heros.get_Race() << endl;
cout << "Sexe : " << heros.get_Sexe() << endl << endl;
 
_getch();
return 0;
} 

 

   Et la fonction main() renvoie la valeur de chaque paramètre (ici une chaîne, bien entendu wink) à l'aide d'une fonction membre d'accès publique appelée également accesseur.

   Vous voyez qu'il n'y a rien de bien difficile à réserver de la mémoire pour des variables membres. Nous pouvons maintenant passer à une nouvelle forme de constructeur d'objet d'une classe, appelé le ''constructeur de copie''.

 


   4 – Constructeur de copie

 

   Hé oui, encore un nouveau type de constructeur ! angelCelui-ci a cependant une importance de taille, nous en parlons de suite. wink

   De même que C++ initialise un constructeur par défaut, même si nous n'écrivons pas le nôtre, le compilateur créera un constructeur de copie par défaut. Mais à quoi va donc servir ce nouveau type de constructeur ? frown

   Son nom l'indique par lui-même, le constructeur de copie va copier un objet à l'identique d'un objet créé par un constructeur par défaut. Comment ? surprise

 

      4.1 – Copie de surface

 

   Voyons un simple exemple de constructeur de copie par défaut créé par le compilateur. Sous cette forme, le compilateur se chargera lui-même de créer une copie des variables membres d'un objet vers un objet créé à partir du premier. Ce type de copie s'appelle ''copie de surface'' et nous en verrons les limites à la suite de notre exemple. wink

   Pour étayer notre propos nous allons dupliquer un objet créé à partir d'une classe ''arme_de_traits'' et donner toute explication nécessaire à la suite de chaque fichier. cool



   Exemple 7 : Projet 101Class_8

      Fichier 101Class_8.h  

 

//projet 101Class_8
//Fichier : 101Class_8.h
//Déclaration de la classe arme_de_Traits
//Copie d'un objet à l'aide du constructeur de copie par défaut
 
#ifndef DEF_101CLASS_8
#define DEF_101CLASS_8
 
#include <iostream>
 
#include <string>
 
using uint = unsigned int;
 
class arme_de_Traits
{
public:
//Constructeur par défaut
arme_de_Traits(std::string nom, std::string type, uint prix_Achat, uint prix_Revente,
uint portee) :_nom(nom), _type(type), _prix_Achat(prix_Achat),
_prix_Revente(prix_Revente), _portee(portee) {}
 
//Destructeur
~arme_de_Traits() {};
 
//Fonctions membres d'accès publique
//Acquisition de données
std::string get_Name() const { return _nom; }
std::string get_Type() const { return _type; }
uint get_PA() const { return _prix_Achat; }
uint get_PV() const { return _prix_Revente; }
uint get_Portee() const { return _portee; }
 
private:
//Données membres privées
std::string _nom;
std::string _type;
uint _prix_Achat;
uint _prix_Revente;
uint _portee;
 
};
#endif 

 

   Le fichier header n'est pas difficile à implémenter. Le constructeur et les méthodes d'accès publique sont définis dans le fichier .h. Comme variables membres de notre arme de traits, nous déclarons les nom, le type d'arme, le prix d'achat, le prix de revente, et la portée de l'arme.

   Nous passons maintenant à notre fichier principal. wink

 

    Fichier 101Class_8mainFile.cpp

 

//projet 101Class_8
//Fichier : 101Class_8mainFile.cpp
//Copie d'un objet à l'aide du constructeur de copie par défaut
 
#include "101Class_8.h"
#include <conio.h>
 
using namespace std;
 
int main()
{
//Création d'un objet de type arme_de_Trait
//Utilisation du constructeur par défaut
arme_de_Traits unArc("Arc long", "Arme Elfe", 220, 22, 55);
 
cout << endl;
//On crée une copie de l'arc à partir de l'objet unArc
cout << "On cree un objet 'autreArc' a partir de l'objet 'unArc'" << endl;
 
arme_de_Traits autreArc(unArc);
 
cout << endl;
cout << "Le constructeur de copie cree par le compilateur copie les variables membres" << endl;
cout << "de 'unArc' vers 'autreArc' et on accede aux membres de la copie a l'aide des" << endl;
cout << "methodes d'acces publique" << endl << endl;
 
cout << "Nom de l'objet autreArc : " << autreArc.get_Name() << endl;
cout << "Type d'arme de l'objet autreArc : " << autreArc.get_Type() << endl;
cout << "Prix d'achat de l'objet autreArc : " << autreArc.get_PA() << endl;
cout << "Prix de vente de l'objet autreArc : " << autreArc.get_PV() << endl;
cout << "Portee de l'objet autreArc : " << autreArc.get_Portee() << endl;
 
_getch();
return 0;
} 

 

   Voilà, il y a déjà pas mal d'explications dans ce fichier pour vous montrer comment se crée une copie de surface. Le constructeur de copie par défaut se borne à copier à l'identique le contenu des variables membres d'un objet vers un autre. Cela semble si simple... angel ''Pas touche'', on laisse faire le compilateur ! wink

   Et le résultat dans la console :

 

  

 

   Un constructeur de copie par défaut est bien suffisant dans de nombreux cas mais qu'en est-il dans le cas où nos variables membres sont des pointeurs réservés dans le tas ? frown

   Nous venons de voir qu'une copie de surface dupliquait les valeurs des variables membres d'un objet vers un autre objet. Il se fait que si des pointeurs pointent sur ces objets, ils pointeront sur le même emplacement mémoire et c'est ici que l'on risque la catastrophe ! surprise Supposons qu'une variable privée de notre classe arme_de_traits soit un pointeur (par exemple std::string *_type) alloué sur le tas, le constructeur de copie par défaut va copier le contenu de la variable _type de l'objet unArc dans la variable membre _type du nouvel objet et ceux-ci pointeront sur la même adresse mémoire. blush

   Et le problème est que si l'on supprime l'objet unArc à l'aide de son destructeur, l'adresse mémoire sur laquelle pointait le pointeur *_type de cet objet sera supprimée et la copie du pointeur *_type de l'objet dupliqué autreArc va vouloir pointer sur un emplacement mémoire qui n'existe plus !! surprise Nous aurons donc créé ce que l'on appelle un ''pointeur fou'' et cela, c'est la ''cata'' assurée. angry

 

      4.2 – Copie en profondeur

 

   La solution est de créer votre propre constructeur de copie qui fera ce qu'on appelle une ''copie en profondeur'', ce qui veut dire que chaque variable membre allouée sur le tas et dupliquée dans un autre objet, se verra automatiquement adresser une mémoire différente de celle allouée à la variable membre créée par le constructeur par défaut. Ce type de constructeur de copie ne prend qu'un seul paramètre, il s'agit d'une référence constante à un objet de la même classe. Une référence constante est fortement recommandée et ce, pour éviter toute modification non désirée de l'objet passé. Nous allons voir tout ceci dans un nouvel exemple. wink

   Nous allons reprendre l'exemple précédent en déclarant les variables comme pointeurs, en réservant leur emplacement mémoire sur le tas et en les initialisant dans le constructeur par défaut.

 

   Exemple 8 : Projet 102Class_9

      Fichier 102Class_9.h 

 

//projet 102Class_9
//Fichier : 102Class_9.h
//Déclaration de la classe arme_de_Traits
//Mise en œuvre d'un constructeur de copie par défaut
 
#ifndef DEF_102CLASS_9
#define DEF_102CLASS_9
 
#include <iostream>
#include <string>
 
using uint = unsigned int;
 
class arme_de_Traits
{
public:
//Constructeur par défaut
arme_de_Traits()
{
_nom = new std::string;
_type = new std::string;
_prix_Achat = new uint;
_prix_Revente = new uint;
_portee = new uint;
 
*_nom = "Arc perforateur de Gobelins";
*_type = "Arc composite";
*_prix_Achat = 300;
*_prix_Revente = 30;
*_portee = 70;
}
 
//Constructeur de copie
arme_de_Traits(const arme_de_Traits &rhs)
{
_nom = new std::string;
_type = new std::string;
_prix_Achat = new uint;
 
_prix_Revente = new uint;
_portee = new uint;
 
*_nom = rhs.get_Name();
*_type = rhs.get_Type();
*_prix_Achat = rhs.get_PA();
*_prix_Revente = rhs.get_PV;
*_portee = rhs.get_Portee();
}
 
//Destructeur
~arme_de_Traits()
{
*_nom = nullptr;
delete _nom;
*_type = nullptr;
delete _type;
*_prix_Achat = 0;
delete _prix_Achat;
*_prix_Revente = 0;
delete _prix_Revente;
*_portee = 0;
delete _portee;
}
 
//Fonctions membres d'accès publique
//Accesseurs
std::string get_Name() const { return *_nom; }
std::string get_Type() const { return *_type; }
uint get_PA() const { return *_prix_Achat; }
uint get_PV() const { return *_prix_Revente; }
uint get_Portee() const { return *_portee; }
 
//Mutateurs
void set_Name(std::string nom) { *_nom = nom; }
void set_PA(uint prix_Achat) { *_prix_Achat = prix_Achat; }
void set_PV(uint prix_Revente) { *_prix_Revente = prix_Revente; }
 
private:
//Données membres privées
std::string *_nom;
std::string *_type;
uint *_prix_Achat;
uint *_prix_Revente;
uint *_portee;
 
};
#endif 

 

   Nous implémentons les constructeurs, destructeur et fonctions membres d'accès publique dans le fichier header. Je rappelle que les implémentations auraient pu se faire dans un fichier .cpp séparé. wink

   Dans notre exemple, les variables membres privées sont des pointeurs. L'emplacement mémoire des variables membres est réservé sur le tas par le constructeur par défaut qui les initialise par la suite. Jusqu'ici il n'y a rien de bien spécial, ce procédé a déjà été vu.

   Nous créons maintenant notre propre constructeur de copie. Nous avons dit un peu plus haut que le constructeur de copie que nous définissons nous-mêmes ne prend en paramètre qu'une référence constante à un objet de la même classe. Notre constructeur de copie est déclaré comme suit :

arme_de_Traits(const arme_de_Traits &rhs)

   arme_de_Traits est bien entendu la classe, et la référence constante à un objet de cette classe est const arme_de_Traits &rhs. Nous utilisons ici la notation anglo-saxonne ''rhs'' qui est l'abréviation de ''right-hand side'' ou ''côté main droite'' en français.

   Nous voyons ensuite que notre constructeur de copie réserve les copies de nos variables membres sur le tas mais celles-ci le seront à des adresses mémoire différentes de celles réservées par le constructeur par défaut. Les pointeurs sont ensuite initialisés en utilisant les méthodes d'accès publique (accesseurs) de notre classe.

   Une expression telle que :

*_nom = rhs.get_Name();

      montre le bien-fondé de la notification rhs de l'objet passé en référence, celui-ci se trouvant à droite du signe = (égal).

   Notez cependant que &rhs n'est qu'une convention établie entre certains programmeurs et que vous auriez tout aussi bien pu donner n'importe quel nom à cette référence, soit ''&autre_Arme'' si ''&rhs'' vous semble quelque peu trop formel. wink

   Et le destructeur commence par assigner les variables de type std::string à nullptr et met les pointeurs de type uint à zéro car nullptr ne peut pas être assigné à une variable unsigned int. Les emplacements mémoire de nos variables membres sont ensuite libérés par l'instruction deletewink

   Pour terminer, nous limitons à trois les fonctions membres d'accès publique d'assignation (mutateurs) de chaque objet car en effet seuls les type et portée de chacun des deux arcs ne peuvent pas changer. Nous pouvons modifier son nom si nous le désirons et bien entendu notre marchand et ami Ethiolas ne se gênerait pas de s'octroyer quelque bénéfice supplémentaire s'il désirait en augmenter le prix. laugh

 

   Fichier 102_9mainFile.cpp 

 

//projet 102Class_9
//Fichier : 102_9mainFile.cpp
//Création d'un objet à l'aide du constructeur de copie par défaut
 
#include "102Class_9.h"
#include <conio.h>
 
using namespace std;
 
int main()
{
//Création d'un objet de type arme_de_Trait
//Utilisation du constructeur par défaut
cout << endl;
cout << "Le constructeur par defaut cree l'objet 'unArc'" << endl;
arme_de_Traits unArc;
 
//On accède aux éléments de l'objet créé par les méthodes d'accès publique
cout << "On accede aux elements de l'objet cree par les methodes d'acces publique" << endl << endl;
 
cout << "L'arc se nomme : " << unArc.get_Name() << endl;
 
cout << "Le type d'arc est : " << unArc.get_Type() << endl;
cout << "Son prix d'achat est de " << unArc.get_PA() << " PO" << endl;
cout << "Son prix de revente est de " << unArc.get_PV() << " PO" << endl;
cout << "Sa portee est de " << unArc.get_Portee() << " metres" << endl;
 
//Maintenant le constructeur de copie crée un second arc à partir du premier
cout << endl;
cout << "Le constructeur de copie duplique l'objet 'unArc'" << endl;
arme_de_Traits autreArc(unArc);
 
//On accède aux éléments de l'objet dupliqué par les méthodes d'accès publique
cout << "On accede aux elements de l'objet duplique 'autreArc' par les methodes" << endl;
cout << "d'acces publique" << endl << endl;
 
cout << "L'arc se nomme : " << autreArc.get_Name() << endl;
cout << "Le type d'arc est : " << autreArc.get_Type() << endl;
cout << "Son prix d'achat est de " << autreArc.get_PA() << " PO" << endl;
cout << "Son prix de revente est de " << autreArc.get_PV() << " PO" << endl;
cout << "Sa portee est de " << autreArc.get_Portee() << " metres" << endl;
 
//On désire changer le nom de l'arc dupliqué
string nom = "Arc de Gondulzak";
cout << endl;
cout << "On change le nom de l'arc duplique" << endl;
 
autreArc.set_Name(nom);
cout << "Son nouveau nom est " << autreArc.get_Name() << endl;
cout << "Mais le nom de l'arc d'origine est toujours " << unArc.get_Name() << endl << endl;
 
//Et nous pourrions modifier le prix de l'arc d'origine ou de la copie de la même
//manière
cout << "Et nous pourrions modifier le prix de l'arc d'origine ou de la " << endl;
cout << "copie de la meme maniere..." << endl;
 
_getch();
return 0;
} 

 

   Nous créons un objet unArc à l'aide du constructeur par défaut et le constructeur surchargé se charge d'en faire une copie exacte (autreArc). Mais cette fois c'est nous qui implémentons chaque membre de la copie à l'aide des méthodes d'accès publique. wink

   Nous voyons que si nous modifions le nom d'un des deux objets, cela n'a pas d'influence sur l'autre. angel

   La grande différence entre cet exemple et l'exemple précédent est que lors de l'appel du destructeur, tous les pointeurs sonts mis à nullptr ou à zéro et les emplacements mémoire sont ensuite libérés par l'instruction delete.

   Aucun pointeur ne peut plus donc pointer vers un emplacement mémoire inexistant et nous évitons de la sorte l'égarement de l'un ou l'autre pointeur dans le programme. indecision

   Ce qui nous donne dans la console :

 


 

 

Remarque : Dans notre exemple, chaque variable membre de l'objet dupliqué est assigné à l'aide des fonctions membres d'accès publique dans le constructeur de copie mais nous aurions pu tout aussi bien le faire en utilisant un pointeur sur chaque variable privée de l'objet dupliqué passé en paramètre.

 

   Cela veut dire qu'à la place de :

*_nom = rhs.get_Name();
*_type = rhs.get_Type();
*_prix_Achat = rhs.get_PA();
*_prix_Revente = rhs.get_PV;
*_portee = rhs.get_Portee();

 

   nous aurions pu écrire :

*_nom = *(rhs._nom);
*_type = *(rhs._type);
*_prix_Achat = *(rhs._prix_Achat);
*_prix_Revente = *(rhs._prix_Revente);
*_portee = *(rhs._portee);

 


   5 – Corrigés des exercices du chapitre 15

 

      Corrigé de l'exercice 1

 

   Pour cet exercice on demandait :

 

   Créez une classe ''arme_de_Traits''. Dans cette classe, implémentez des méthodes d'accès publique pour le nom et la portée de l'arme.

   Dans la fonction main() vous créerez un objet ''monArc'' qui affichera les nom et portée de l'arme à l'aide des méthodes d'accès publique correspondantes.
   Le nom et la portée de l'arme seront saisis au clavier dans la fonction main().

 

   Fichier Chap15Ex_1.h :

 

 

//projet Chap15eXERCICE-1
//Fichier : Chap15Ex_1.h
//Déclaration de la classe arme_de_Traits
#ifndef DEF_CHAP15EX_1
#define DEF_CHAP15EX_1
 
#include <iostream>
#include <string>
 
using uint = unsigned long;
 
class arme_de_Traits
//déclare la classe arme_de_Traits
{
//méthodes d'accès publiques
public:
std::string get_Name();
void set_Name(std::string name);
 
uint get_Portee();
void set_Portee(uint portee);
 
//données membres privées
private:
uint _portee;
std::string _name;
 
};
#endif 

 

  Dans ce fichier header nous ne déclarons aucun constructeur, le compilateur s'en chargera pour nous. Nous déclarons simplement des méthodes d'accès publique qui nous permettront de saisir et d'afficher les nom et la portée d'un objet arme_de_Traits.

 

      Fichier Chap15Ex_1.cpp : 
 

 

//projet Chap15Exercice_1
//Fichier : Chap15Ex_1.cpp
//Implémentation des méthodes d'accès publique set_Name() et get_Name()
 
#include "Chap15Ex_1.h"
#include <string>
using namespace std;
 
 
//définition de set_Name()
//fonction d'accès publique
//définit la variable privée _name
void arme_de_Traits::set_Name(string name)
{
//initialise la variable membre _age
//avec la valeur passée par le paramètre age
_name = name;
}
 
 
//get_Name(), méthode d'accès publique
//renvoie la valeur de la variable privée _name;
string arme_de_Traits::get_Name()
{
return _name;
}
 
 
//définition de set_Portee()
//fonction d'accès publique
 
//définit le membre _age
void arme_de_Traits::set_Portee(uint portee)
{
//initialise la variable membre _age
//avec la valeur passée par le paramètre age
_portee = portee;
}
 
//get_Portee(), méthode d'accès publique
//renvoie la valeur du membre _age;
uint arme_de_Traits::get_Portee()
{
return _portee;
} 

 

  Dans ce fichier nous implémentons les méthodes membres d'accès publique. Tous les commentaires nécessaires se trouvent déjà dans le code. wink

 

      Fichier Chap15Ex_1main.cpp : 

 

//projet Chap15Exercice_1
//Fichier : Chap15Ex_1main.cpp
//Fonction main()
#include "Chap15Ex_1.h"
#include <conio.h>
using namespace std;
 
int main()
{
//Création d'un objet de type arme_de_Traits
arme_de_Traits monArc;
string nom;
uint portee;
 
cout << endl;
 
cout << "Entrez le nom de votre arc : ";
getline(cin, nom);
monArc.set_Name(nom);
cout << "Entrez la portee : ";
cin >> portee;
 
monArc.set_Portee(portee);
 
//Acquisition de la variable privée _nom par l'intermédiaire de la méthode d'accès
// get_Name()
cout << endl;
cout << "Acquisition de la variable privee _nom par l'intermediaire de la" << endl;
cout << "methode d'acces get_Name()" << endl << endl;
cout << "Le nom de mon arc est : " << monArc.get_Name() << endl;
 
//Acquisition de la variable privée _portee par l'intermédiaire de la méthode d'accès
// get_Portee()
cout << endl;
cout << "Acquisition de la variable privee _portee par l'intermediaire de la" << endl;
cout << "methode d'acces get_Portee()" << endl << endl;
cout << "Sa portee est de " << monArc.get_Portee() << " metres" << endl;
 
_getch();
return 0;
} 

 

   Dans notre fichier principal, nous créons un objet monArc de type arme_de_Traits. On déclare ensuite deux variables, ''nom'' et ''portee''. Il est ensuite demandé à l'utilisateur d'entrer les nom et la portée d'un arc. Ceux-ci sont acquis à l'aide des méthodes d'accès set_Name() et set_Portee() et sont affichés par l'intermédiaire des méthodes d'accès get_Name() et get_Portee().

 

  Corrigé de l'exercice 2 (un petit challenge)

 

   Pour cet exercice je vous demanderai de bien vouloir vous rapporter à son énoncé dans le chapitre 15. Je vous remercie de votre compréhension.

 

      Fichier Chap15Ex_2.h :

 

//projet Chap15Exercice_2
//Fichier : Chap15Ex_2.h
//Déclaration de la classe Personnage
#ifndef DEF_CHAP15EX_2
#define DEF_CHAP15EX_2
 
#include <iostream>
#include <string>
#include <vector>
 
using namespace std;
 
class Personnage
{
public:
//Constructeur par défaut à qui l'on passe une référence sur un vector string
Personnage(vector<std::string>&noms);
 
//Destructeur
~Personnage();
 
//méthodes d'accès publiques
std::string get_Name() const;
void set_Name(std::string nom);
 
//fonction membre
void lire_noms(vector<std::string> &) const;
 
private:
std::string _name;
 
};
#endif 

 

   Dans le fichier header, nous créons une classe Personnage. La classe se compose d'un constructeur par défaut à qui l'on passe une référence sur un vector string. Suivent les déclarations de deux méthodes d'accès publique, l'une, std::string get_Name() const, pour la récupération d'une chaîne et une seconde, void set_Name(std::string nom), pour la saisie d'une chaîne.

   Une fonction membre lire_noms() à laquelle on passe une référence sur un vector string est ensuite déclarée.

 

      Fichier Chap15Ex_2.cpp : 

 

//projet Chap15Exercice_2
//Fichier : Chap15Ex_2.cpp
//Implémentation du constructeur et des méthodes
 
#include "Chap15Ex_2.h"
 
//constructeur de Personnage
//ajoute deux noms dans le vector noms
Personnage::Personnage(vector<string>&noms)
{
noms.push_back("Aria");
noms.push_back("Godefroid");
}
 
//destructeur de Personnage
Personnage::~Personnage() {}
 
//Méthodes d'accès publiques
void Personnage::set_Name(string nom)
{
//initialise la variable membre _name
//avec la valeur passée par le paramètre nom
_name = nom;
}
 
string Personnage::get_Name() const { return _name; }
 
//Fonction membre
void Personnage::lire_noms(vector<string>&noms) const
{
for (auto &i : noms)
cout << i << endl;
} 

 

   Le fichier Chap15Ex_2.cpp implémente le constructeur ainsi que les fonctions membres. Le constructeur de Personnage est initialisé en ajoutant les noms ''Aria'' et ''Godefroid'' dans le vector noms. L'implémentation des méthodes d'accès publique est connue. La fonction lire_noms(), quant-à-elle, utilise la boucle for du C++ 11 telle que nous l'avons déjà vue plusieurs fois auparavant. wink

 

      Fichier Chap15Ex_2main.cpp : 

 

//projet Chap15Exercice_2
//Fichier : Chap15Ex_2main.cpp
//Implémentation de la fonction main()
//On ajoute 2 noms dans le vector et on affiche le tout
 
#include "Chap15Ex_2.h"
#include <conio.h>
 
using namespace std;
 
int main()
{
//Déclaration d'un vector vide
vector<string>noms;
 
//Création d'un objet de type Personnage
//à l'aide du constructeur par défaut
Personnage unHeros(noms);
 
//On ajoute les noms Elwyn et Aron dans notre vector string
unHeros.set_Name("Elwyn");
noms.push_back(unHeros.get_Name());
unHeros.set_Name("Aron");
noms.push_back(unHeros.get_Name());
 
//Nous aurions pu ajouter directement les noms
//dans le vector de cette manière
// noms.push_back("Elwyn");
// noms.push_back("Aron");
//mais nous avons utilisé les méthodes d'accès publiques
//afin de bien comprendre leur fonctionnement.
 
cout << endl;
cout << "Les noms saisis dans le vector sont : " << endl << endl;
unHeros.lire_noms(noms);
 
cin.get();
return 0;
 
} 

 

   Le fichier principal déclare un vector vide dénommé ''noms''. Un objet ''unHeros'' de type Personnage est ensuite créé avec lequel nous utilisons les méthodes membres d'accès publique afin d'ajouter les noms ''Elwyn'' et ''Aron'' dans le vector noms. Les commentaires du fichier se suffisent à eux-mêmes pour expliquer la manière utilisée pour ajouter des données dans le vector.

   Pour terminer, les noms saisis dans le vector sont affichés à l'aide de la fonction membre lire_noms().

   Ce qui donne dans la console :

 

 

  

   Et voilà, c'est tout en ce qui concerne ce chapitre. cool

   Les corrigés des exercices de ce chapitre seront présentés à la fin du chapitre 17.

      @ bientôt pour le chapitre 17 – Introduction aux Classes (3).                                                                     

            Gondulzak.  angel
 
 
 

 

Connexion

CoalaWeb Traffic

Today109
Yesterday240
This week1420
This month5094
Total1744301

28/04/24