[Archive — École 42] C++ Modules 03 à 06
💡Les modules 03 à 06 de la C++ de l’école 42 permettent d’appréhender rapidement les notions suivantes :
- Héritage
- Le polymorphisme par sous typage
- Les méthodes pures, classes abstraites et interfaces
- Les classes imbriquées
- La gestion des exceptions (erreurs)
- Les différents types de cast
Héritage
https://cdn.intra.42.fr/video/video/117/piscine_c___-_d03_-_00_heritage.mp4
En programmation orientée objet, l’héritage est un mécanisme qui permet, lors de la déclaration d’une nouvelle classe (classe dérivée / classe fille), d’y inclure les caractéristiques d’une autre classe (classe de base / classe mère).
Lorsqu’une derived class hérite d’une base class, elle accède à ses attributs d’encapsulation “public” et “protected”.
Une classe fille (dérivée) peut “overrider” (et c’est souvent ce qui est fait) les “comportements” (fonctions membres) de la classe mère (voir polymorphisme par sous typage).
À noter qu’une classe peut hériter de plusieurs classes (on parle alors d’héritage multiple).
Cela peut potentiellement poser des problématiques d’implémentation, on pourra parler d’héritage “en diamant”, par exemple.
Le polymorphisme par sous typage
Aussi appelé polymorphisme par héritage, à ne pas confondre avec la surcharge (= polymorphisme ad hoc, fonctions -de même nom- au sein d’une seule classe avec différents paramètres), permet de définir une interface unique qui sera implémentée par une “routine”(fonction) membre dans chaque classe faisant partie de la même “hiérarchie d’héritage”.
https://cdn.intra.42.fr/video/video/119/piscine_c___-_d04_-_00_polymorphismes_et_sous-typages.mp
#include <string>
#include <iostream>
class Character
{
public:
virtual void sayHello(std::string const &target)
{
std::cout << "Hello " << target << " !" << std::endl;
return ;
};
};
class Warrior: public Character
{
public:
virtual void sayHello(std::string const &target)
{
std::cout << "Back off " << target << " !" << std::endl;
return ;
};
};
class Cat
{
//...
};
int main(void)
{
Warrior *a = new Warrior();
// Will also work, will work with the Character behavior without de virtual keyword
Character *b = new Warrior();
// Won't work
// Warrior *c = new Character();
a->sayHello("students");
b->sayHello("students");
}
Lorsque l’on implémentera des fonctions via le polymorphisme par sous typage, on préférera souvent utiliser le mot clé “virtual”.
On appellera aussi ces fonctions des méthodes.
Le mot clé virtual va permettre d’avoir un linkage dynamique et non plus statique / à la compilation (”at runtime” / à l’exécution).
#include <string>
#include <iostream>
class Character
{
public:
virtual void sayHello(std::string const &target)
{
std::cout << "Hello " << target << " !" << std::endl;
return ;
};
};
class Warrior: public Character
{
public:
virtual void sayHello(std::string const &target)
{
std::cout << "Back off " << target << " !" << std::endl;
return ;
};
};
class Cat
{
//...
};
int main(void)
{
Warrior *a = new Warrior();
// Will also work, will work with the Character behavior without de virtual keyword
Character *b = new Warrior();
// Won't work
// Warrior *c = new Character();
a->sayHello("students");
b->sayHello("students");
}
Grâce à la résolution dynamique, le compilateur comprendra (dans l’exemple précédent) que notre variable b pointe bien sur un Warrior et non un Character.
De plus, notez que l’utilisation de virtual vous permettra d’éviter d’avoir des fuites de mémoire (leaks) dans ce cas précis (problèmes au niveau du destructeur appelé si la résolution est statique).
Pour rappel, on appellera donc méthode est donc une fonction membre à lien / résolution dynamique.
Les méthodes pures, classes abstraites et interfaces
Une méthode pure est une méthode suffixée par = 0.
Cela signifie que cette méthode (au sein de la base class) ne peut pas être implémentée. Il faudra donc que le développeur l’implémente au sein des classes dérivées (filles).
Cela implique également que la classe mère ne pourra pas être instanciée.
Cette classe (qui possède une méthode pure) devient donc une classe abstraite, que l’on préfixe par convention A (exemple: ACharacter).
#include <string>
#include <iostream>
class ACharacter
{
public:
// Can't be implemented in the base class
virtual void attack(std::string const &target) = 0;
void sayHello(std::string const &target);
};
class Warrior: public ACharacter
{
public:
virtual void attack(std::string const &target);
};
Dans une classe abstraite, certains comportements ne sont pas définis (exemple avec = 0), cependant d’autres comportements peuvent l’être (toutes les fonctions ne sont pas nécessairement des méthodes pures).
Une classe dérivée qui n’implémenterait pas les comportements mentionnés dans la classe parent ne pourrait pas non plus être instanciée.
Interface
Par convention, une classe peut également être une “interface” (préfixée avec un I, par convention).
Dans ce cas là (d’après ma compréhension de la définition du tutoriel de l’école, qui ne semble pas être partagée par toutes les sources), toutes fonctions sont des méthodes pures.
A noter que ces fonctions sont, de plus, toutes publiques.
class ICoffeeMaker
{
public:
virtual void fillWaterTank(IWaterSource *src) = 0;
virtual ICoffee *makeCoffee(std::string const &type) = 0;
};
Les classes imbriquées
https://cdn.intra.42.fr/video/video/120/piscine_c___-_d05_-_00_classes_imbriquer.mp4
class Cat
{
public:
Cat(void);
virtual ~Cat(void);
class Leg
{
Leg(void);
virtual ~Leg(void);
//
};
};
int main(void)
{
Cat somecat();
Cat::Leg someleg();
return (0);
}
💡 Il est possible de définir une classe dans la définition / le scope d’une autre classe (un peu comme s’il s’agissait d’un namespace).
Cette notion a été rapidement présentée mais je n’ai pas rencontré son utilité durant le cursus.
La gestion des exceptions (erreurs)
https://cdn.intra.42.fr/video/video/121/piscine_c___-_d05_-_01_les_exceptions.mp4
Lorsque l’on souhaite indiquer une erreur, il nous sera possible de faire ce que l’on appelle : (remonter) une exception, en C++.
En C, nous utilisions les retours de fonctions pour déterminer si une erreur était intervenue.
Cette technique est toujours recommandée (car la gestion des exceptions est plus gourmande en ressources) pour des cas d’erreurs “génériques”.
Cependant, les exceptions seront particulièrement utiles dans certains cas, notamment en cas d’erreurs d’appels système par exemple (new, delete, open…).
Pour gérer correctement ces erreurs qui seraient remontées, il faut appeler les fonctions susceptibles d’en retourner au sein de ce que l’on appelle un try / catch block.
Il existe différents types d’exception. Le type std::exception
est le type le plus “basique” d’exception renvoyée en C++.
Il est, de plus, possible de définir ses propres types d’erreurs (qui pourront / devront hériter publiquement de std::exception).
class CustomException: public std::exception
{
public:
virtual const char * what() const throw()
{
return "Error";
}
};
Si cette erreur survient à l’intérieur d’un scope/block “try”, nous allons directement passer à l’intérieur du scope catch.
try
{
test3();
}
catch (CustomerException &e)
{
// Handle error
}
catch (std::exception &e)
{
// Handle other error differently
}
// Allow to catch any type of exception
catch (...)
{
//...
}
Comme suggéré ci-dessus, il est possible de “catcher” plusieurs types d’erreurs (et de définir ainsi différents comportements pour les gérer).
Les différents types de cast
Le C++ permet de gérer le casting (conversion de type) de différentes façons, bien différentes du cast en C (toujours faisable).
- Static cast
- Dynamic cast
- Reinterpret cast
- Const cast
Ces notions sont relativement complexes, et à mon sens peu utilisées / oubliées durant le cursus, c’est pourquoi je vous renvoie simplement aux vidéos si vous voulez plus d’informations.
https://cdn.intra.42.fr/video/video/127/piscine_c___-_d06_-_04_static_cast_late_1__Output_1_.mp4
https://cdn.intra.42.fr/video/video/133/piscine_c___-_d06_-_05_dynamic_cast.mp4
https://cdn.intra.42.fr/video/video/128/piscine_c___-_d06_-_06_reinterpret_cast.mp4
https://cdn.intra.42.fr/video/video/128/piscine_c___-_d06_-_06_reinterpret_cast.mp4