Date de première publication : 2013/11/11
Dans ce TP, nous allons nous intéresser à quelques révisions et quelques concepts que nous n'avons pas encore vus.
Références
class MemePasPeur {
int tab[4];
public:
MemePasPeur() {
tab[0] = 1;
}
const int & val() const {
return tab[0];
}
void modify() {
tab[0] = 4;
}
};
int main(int, char **) {
MemePasPeur * p = new MemePasPeur;
int i = p->val();
const int & j = p->val();
std::cout << i << " " << j << std::endl;
p->modify();
std::cout << i << " " << j << std::endl;
delete p;
std::cout << i << " " << j << std::endl;
return 0;
}
Compilez, exécutez le programme ci-dessus. Que se passe-t-il ? Pourquoi ?
Nous avons évoqué le problème en cours. Il suffit d'exécuter valgrind pour voir l'erreur qui est faite !
Héritage simple
Exercice 1
class Mere {
public:
Mere() {
// cout << "Mere::Mere()" << endl;
tab = new int[100];
}
~Mere() {
// cout << "Mere::~Mere()" << endl;
delete [] tab;
}
protected:
int * tab;
};
class Fille : public Mere {
public:
Fille() {
// cout << "Fille:Fille()" << endl;
tab = new double[100];
//Mere::tab[50] = 4;
}
~Fille() {
// cout << "Fille::~Fille()" << endl;
delete [] tab;
}
protected:
double * tab;
};
int main(int, char**) {
Mere * m = new Fille();
cout << "penser a valgrind" << endl;
delete m;
return 0;
}
- Copier et compiler le programme ci-dessus
- Exécuter et faire ce qu'il est demandé.
- Que se passe-t-il ?
- Régler le problème
Il ne faut pas toucher à la fonction main()
. Le destructeur d'une classe est une méthode comme les autres !
Exercice 2
class Mere {
public:
Mere() { m(); }
virtual void m() { cout << "mm" << endl; }
virtual ~Mere() {}
};
class Fille : public Mere {
public:
Fille() { m(); }
virtual void m() { cout << "mf" << endl; }
virtual ~Fille() {}
};
Instanciez la classe Fille
. Que constatez-vous ? Qu'est-ce que cela veut dire ?
Pour rappel, voici un exemple de virtualité fonctionnelle :
Mere * o = new Fille;
o->m();
Là encore, c'est un exemple du cours. Il n'y a pas d'appel polymorphique dans un constructeur !
Héritage multiple
L'héritage multiple est très pratique. La classe fille hérite de tous les membres de ses parents. Voici la syntaxe :
class Enfant : public Mere, public Pere {
// héritage de tous les attributs et méthodes usuels
};
Modèle très simple
Nous devons aider à faire le logiciel de gestion de la société qui fabrique Robocop (OCP pour les intimes). [ Quand on vous dit que l'héritage multiple est un concept utile et très naturel !] La société gère des humanoïdes qui sont à la fois humains et machines.
Concernant les humains, il est nécessaire de connaître le nom, le genre et l'âge. Pour la machine, il est intéressant de connaître le type du modèle, son autonomie, sa réparabilité (note attribuée par iFixit sur 10). Modélisez l'humanoïde avec l'héritage multiple et instanciez quelques produits :-). On pourra s'intéresser à la date de création de l'humanoïde et la date de sa dernière recharge
Voici quelques tests :
TEST_CASE("Humain1") {
const char * nom ="Alex";
const Humain alex(nom, HOMME, 35);
CHECK( nom == alex.getNom() );
CHECK( HOMME == alex.getGenre() );
// ou enum class Genre.HOMME
CHECK( 35 == alex.getAge() );
}
TEST_CASE("Humain2") {
Humain thomas("thomas", HOMME, 26);
thomas.setNom("conchita");
thomas.setAge(27);
thomas.setGenre(FEMME);
CHECK( "conchita" == thomas.getNom() );
CHECK( FEMME == thomas.getGenre() );
CHECK( 27 == thomas.getAge() );
}
TEST_CASE("Machine") {
Machine stylet("stylet Apple", 2*24*3600, 1);
CHECK( "stylet Apple" == stylet.getType() );
CHECK( 2*24*3600 == stylet.getAutonomie() );
CHECK( 1 == stylet.getIfixit() );
}
TEST_CASE("Robocop") {
Humanoide robocop("Murphy", "Robocop 1.0", HOMME, 35);
CHECK( "Murphy" == robocop.getNom() );
CHECK( "Robocop 1.0" == robocop.getType() );
CHECK( HOMME == robocop.getGenre() );
CHECK( 35 == robocop.getAge() );
CHECK( 3 == robocop.getIfixit() );
}
TEST(Humain, Test1) {
const Humain alex("Alex", HOMME, 35);
EXPECT_STREQ(alex.getNom().c_str(), "Alex");
EXPECT_EQ(alex.getGenre(), HOMME);
EXPECT_EQ(alex.getAge(), 35);
}
TEST(Humain, Test2) {
Humain thomas("thomas", HOMME, 26);
thomas.setNom("conchita");
thomas.setAge(27);
thomas.setGenre(FEMME);
EXPECT_STREQ(thomas.getNom().c_str(), "conchita");
EXPECT_EQ(thomas.getGenre(), FEMME);
EXPECT_EQ(thomas.getAge(), 27);
}
TEST(Machine, Test3) {
Machine stylet("stylet Apple", 2*24*3600, 1);
EXPECT_STREQ(stylet.getType().c_str(), "stylet Apple");
EXPECT_EQ(stylet.getAutonomie(), 2*24*3600);
EXPECT_EQ(stylet.getIfixit(), 1);
}
TEST(Robocop, Test4) {
Humanoide robocop("Murphy", "Robocop 1.0", HOMME, 35);
EXPECT_STREQ(robocop.getNom().c_str(), "Murphy");
EXPECT_STREQ(robocop.getType().c_str(), "Robocop 1.0");
EXPECT_EQ(robocop.getGenre(), HOMME);
EXPECT_EQ(robocop.getAge(), 35);
EXPECT_EQ(robocop.getIfixit(), 3);
}
Si l'on estime que le nom de l'humain et le type du modèle sont en fait deux informations équivalentes (qui pourraient avoir le même nom
dans chacune des classes Humain
et Machine
)
, comment peut-on coder cela avec le minimum de code ?
Mettre en place une classe Commun
qui contient les informations communes à Humain
et Machine
et faire hériter Humain
et Machine
de Commun
.
Un autre modèle
Vous avez été embauché(s, e, es) pour travailler sur un logiciel de recensement. Il faut écrire le noyau de l'application (les classes) en fonction de ce que l'on va vous décrire...
Chaque individu a un nom, une intelligence, une force, une vitesse, une vitalité, ... un potentiel de magie. La population, telle que l'on la connait, est constituée d'humains, d'elfes et d'orcs. Certains individus ont des caractéristiques exceptionnelles et méritent un traitement particulier : les sorciers ou les soldats par exemple.
- Proposer une classe
Individu
qui reprendra les caractéristiques citées ci-dessus. - Écrire une classe
Humain
qui hérite d'Individu
. Le constructeur permet de donner un nom, les autres caractéristiques sont par défaut : pas trop intelligent, ni fort, ni rapide, ni trop sensible à la magie - Comment s'arranger pour que la classe
Individu
ne soit pas instantiable par tout à chacun sans qu'elle soit abtraite ? - Proposer une classe
Elfe
où les caractéristiques par défaut sont bien plus élevées que lesHumain
s - Proposer une classe
Orc
: un orc n'est pas forcément intelligent mais il tape vraiment très fort (en passant, il n'a jamais été observé d'orc faisant de la magie... sauf un orc barde mais même là, on n'est pas sûr d'avoir été abusé par un sorcier non orc à l'humour douteux!
Pour que la classe ne soit pas instantiable par tout à chacun, il suffit que le ou les constructeurs ne soit(ent) pas public(s) :-)
Nous allons maintenant nous intéresser aux caractéristiques exceptionnelles des individus, par exemple leur faculté à faire de la magie ou à taper (les héros / brutes ...)
- Proposer une classe
Sorcier
abstraite dont la seule méthode seraensorceler()
- Proposer une classe
Soldat
abstraite dont la seule méthode seracombattre()
- Écrire une classe
Mage
des humains pratiquant la magie. Un humain a besoin de parler pour faire de la magie. - Écrire une classe
Chaman
des elfes pratiquant la magie (enfin, plus que la moyenne, et de loin). Les chamans ne parlent pas pour faire de la magie ! - Proposer un conteneur pour référencer tous les sorciers quelle que soit leur espèce
- Vérifier qu'un parcours des éléments est possible en appelant la méthode
ensorceler()
de chaque élément.
Héritage à répétition
class A {
public:
A() { cout << "A ";}
~A() { cout << "~A ";}
};
class B : public A {
public:
B() { cout << "B "; }
~B() { cout << "~B ";}
};
class C : public A {
public:
C() { cout << "C ";}
~C() { cout << "~C ";}
};
class D : public B, public C {
public:
D() { cout << "D ";}
~D() { cout << "~D ";}
};
int main(int, char**) {
D d;
cout << "C'est Fini !";
return 0 ;
}
- Vérifier que tout membre (attribut ou méthode) défini dans A est ambigu dans D
- Mettre en place l'héritage virtuel pour lever ces problèmes et que le constructeur de A ne soit appelé qu'un nombre minimal de fois.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10