1 ● Conception ● Exemple de classes ● Fonctions virtuelles ● Fonctions virtuelles pures ● Classes abstraites Programmation en C++ Héritage
2 Relations entre les classes « Hérite »... ● La relation « Hérite » (“Is a“) – Une classe (A) peut hériter de l'état (des variables) et du comportement (des méthodes) d'une autre classe (B) – On dit que la classe A représente une classe de base (ou un super- type (super-classe) ou une généralisation) de la classe B, représentant une classe dérivée (ou un sous-type (sous-classe) ou une spécialisation) – Par exemple les leptons, hadrons, bosons sont des particules, mais appartenant à des groupes différents, chacun ayant ses propriétés spécifiques LeptonHadron Particle Boson
3 Exemple ● Pour démontrer la conception et la mise en œuvre de l'héritage, nous allons développer des classes qui décrivent les animaux de compagnie dans une famille habituelle ● Essayons de trouver des caractéristiques communes pour nos animaux typiques – le chat et le chien – Chacun a son nom – Il faut qu'on leur donne à manger – ils mangent – Il nous “parlent” – le chien aboie, le chat miaule ● Les comportements spécifiques: – Le chat chasse les souris – Le chien monte la garde de la maison
4 La conception Dog name Eat() Speak() Cat name Eat() Speak() Les données membres et les fonctions communes peuvent être extraites dans une classe de base Pet (Les comportements spécifiques seront considérés plus tard) Pet name Eat() Speak() Dog Cat
5 Les Classes (définition) #include class Pet { public: Pet(std::string name); void Eat(); void Speak(); std::string Name(); private: std::string m_name; }; #include “Pet.h” #include class Dog : public Pet { public: Dog(std::string name); }; Dog Cat Pet name Eat() Speak() #include “Pet.h” #include class Cat : public Pet { public: Cat(std::string name); }; La déclaration de la relation de l'héritage
6 Les Classes (implémentation) #include "Pet.h" #include Pet::Pet(std::string name) : m_name(name) { } void Pet::Eat() { std::cout << m_name << " eats" << std::endl; } void Pet::Speak() { std::cout << m_name << " speaks" << std::endl; } std::string Pet::Name() { return m_name; } Initialisation de la classe de base #include "Dog.h" #include Dog::Dog(std::string name) : Pet(name) { } #include "Cat.h" #include Cat::Cat(std::string name) : Pet(name) { } Dog Cat Pet name Eat() Speak()
7 Les Classes (utilisation)... #include "Dog.h" #include "Cat.h" int main() { Dog dog("Lassie"); Cat cat("Tilly"); Cat cat2("Tom"); // When you arrive at home pets welcome you dog.Speak(); cat.Speak(); cat2.Speak(); // We have to feed them dog.Eat(); cat.Eat(); cat2.Eat(); } main1.cx x Déclaration des objets avec leurs types spécifiques (par des sous-classes) Appel de fonctions définies dans la classe de base [localhost]>./atHome1 Lassie speaks Tilly speaks Tom speaks Lassie eats Tilly eats Tom eats
8... Les Classes (utilisation) #include "Dog.h" #include "Cat.h" #include int main() { // The collection of our pets std::vector ourPets; ourPets.push_back(new Dog("Lassie")); ourPets.push_back(new Cat("Tilly")); ourPets.push_back(new Cat("Tom")); // When you arrive at home pets welcome you std::vector ::iterator it; for ( it=ourPets.begin(); it != ourPets.end(); it++ ) { Pet* pet = *it; pet->Speak(); } /// We have to feed them for ( it=ourPets.begin(); it != ourPets.end(); it++ ) { Pet* pet = *it; pet->Eat(); } main.cx x Utilisation des objets par leurs type de base (Pet*) L'existence de la classe de base nous permet de nous adresser à nos objets de façon générique [localhost]>./atHome Lassie speaks Tilly speaks Tom speaks Lassie eats Tilly eats Tom eats
9 Surcharge de fonctions... ● Nous allons préciser notre modèle: – Le chien mange des croquettes ProPlan – Le chat mange des boites Whiskas – Le chien aboie – Le chat miaule ● Modélisation: Chaque sous-classe redéfinit des fonctions de la classe de base Dog Eat() Speak() Cat Eat() Speak() Pet name Eat() Speak()
10... Surcharge de fonctions... #include “Pet.h” #include class Dog : public Pet { public: Dog(std::string name); virtual void Eat(); virtual void Speak(); }; #include “Pet.h” #include class Cat : public Pet { public: Cat(std::string name); virtual void Eat(); virtual void Speak(); }; La déclaration de la relation de héritage ● Nous allons ajouter ces méthodes à des classes Dog et Cat: ● Pour que la « bonne » fonction soit appelée il faut la déclarer virtual – La « bonne » fonction = la fonction définie pour la classe dont le type dynamique de notre objet est ● L'objet défini par le Pet* pointer est du type Dog ou du type Cat – La fonction virtuelle agit comme une interface pour les fonctions définies dans la classe de base et dans toutes les classes dérivées #include class Pet { public: Pet(std::string name); virtual void Eat(); virtual void Speak(); private: std::string m_name; }; Il n'est pas obligatoire de re-déclarer les fonctions virtuelles dans des classes dérivées mais c'est une bonne pratique de le faire
11... Surcharge de fonctions... La déclaration de la relation de héritage Cat::Eat() { std::cout << Name() << " eats the cans Whiskas “ << std::endl; } void Cat::Speak() { std::cout << Name() << ": meow, meow !! " << std::endl; } Dog::Eat() { std::cout << Name() << " eats the granules ProPlan " << std::endl; } void Dog::Speak() { std::cout << Name() << ": woof, woof !! " << std::endl; } Dans l'implantation de ces fonctions, il n'y a rien de spécial:
12... Surcharge de fonctions [localhost]>./atHome Lassie speaks Tilly speaks Tom speaks Lassie eats Tilly eats... [localhost] >./atHome1 Lassie: woof, woof !!! Tilly: meow, meow !!! Tom: meow, meow !!! Lassie eats the granules ProPlan Tilly eats the cans Whiskas... On compile et exécute les deux programmes: Si déclaration des objets avec leur types spécifiques (par des sous-classes) Si déclaration des objets avec le type générique (par la super-classe) Quand les fonctions ne sont pas déclarées virtual, c'est la fonction de type déclaré, qui est appelée, sans prendre en compte le type réel d'objet [localhost] >./atHome Lassie: woof, woof !!! Tilly: meow, meow !!! Tom: meow, meow !!! Lassie eats the granules ProPlan Tilly eats the cans Whiskas... Quand les fonctions sont déclarées virtual, c'est la fonction de type réel d'objet, qui est appelée
13 Si on achète un canari Ajoute d'une nouvelle classe dérivée de la classe Pet est trivial: #include “Pet.h” #include class Canary : public Pet { public: Canary(std::string name); }; #include "Canary.h" #include Canary::Canary(std::string name) : Pet(name) { } //... #include "Canary.h" //... int main() { //... ourPets.push_back(new Canary("Robbie")); //... } main.cx x [localhost]>./atHome Lassie: woof, woof !!! Tilly: meow, meow !!! Tom: meow, meow !!! Robbie speaks Lassie eats the granules ProPlan Tilly eats the cans Whiskas Tom eats the cans Whiskas Robbie eats La classe Canary ne spécifie pas des méthodes de la classe de base
14 Fonctions virtuelles pures... ● Nous allons de nouveau ré-examiner les exigences de notre programme ● Imaginons que nous allons confier nos bêtes à notre voisin et lui demander de nous remplacer pendant nos vacances – Notre programme l'aidera à connaître les besoins de nos animaux: – Mais qu'est-ce que mange Robbie? ● Notre classe de base nous a permis de ne pas spécifier les besoins de notre canari [localhost]>./atHome Lassie: woof, woof !!! Tilly: meow, meow !!! Tom: meow, meow !!! Robbie speaks Lassie eats the granules ProPlan Tilly eats the cans Whiskas Tom eats the cans Whiskas Robbie eats
15... Fonctions virtuelles pures... ● La classe de base peut obliger toutes les classes dérivées à implanter une fonction – Pour cela la fonction doit être déclarée comme une fonction virtuelle pure: #include class Pet { public: Pet(std::string name); virtual void Eat() = 0; virtual void Speak(); private: std::string m_name; }; void Pet::Eat() { std::cout << m_name << " eats" << std::endl; } Fonction déclarée virtuelle pure; elle n'a pas besoin de la définition
16... Fonctions virtuelles pures On compile notre programme... et il ne se compile pas ! – Il est impossible de créer une instance de la classe qui n'a pas défini toutes les fonctions virtuelles pures héritées Les classes desquelles on ne peut pas créer d' instance s'appellent les classes abstraites [localhost] > g++ -I. *.cxx -o atHome main.cxx: In function ‘int main()’: main.cxx:10: error: cannot allocate an object of abstract type ‘Canary’ Canary.h:4: note: because the following virtual functions are pure within ‘Canary’: Pet.h:10: note: virtual void Pet::Eat()
17 Fonctions spécifiques... ● Nous allons considérer les comportements spécifiques: – Le chat chasse les souris – Le chien monte la garde de la maison Dog name Eat() Speak() Guard() Cat name Eat() Speak() HuntMice Les fonctions spécifiques sont ajoutées seulement dans les classes dérivées Pet name Eat() Speak() Dog Guard() Cat HuntMice()
18... Fonctions spécifiques #include "Dog.h" #include "Cat.h" int main() { Dog dog("Lassie"); Cat cat("Tilly"); Cat cat2("Tom"); // When you arrive at home pets welcome you dog.Speak();... // We have to feed them dog.Eat();... // And everyone continues in their work dog.Guard(); cat.HuntMice(); } main1.cx x main.cx x #include "Dog.h" #include "Cat.h" #include int main() { // The collection of our pets std::vector ourPets; ourPets.push_back(new Dog("Lassie"));... // When you arrive at home pets welcome you // We have to feed them // And everyone continues in their work for ( it=ourPets.begin(); it != ourPets.end(); it++ ) { Pet* pet = *it; Dog* dog = dynamic_cast (pet); If ( dog ) dog->Guard(); Cat* cat = dynamic_cast (pet); If ( cat ) cat->HuntMice(); } Les fonctions spécifiques ne peuvent pas être appelées de façon générique
19 Conclusions L'héritage des classes nous permets ● Éviter la duplication du code commun – Par implantation commune dans la classe de base ● Traiter des objets de façons générique – Les collections des objets par le type de classe de base – Appel des fonctions par l'interface commune ● En déclarant des fonctions virtuelles – Obliger des classes dérivées d'implanter les interfaces obligatoires ● En déclarant des fonctions virtuelles pures