Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
poo [2016/02/17 10:02] panda_w [Créer des classes] |
poo [2019/12/27 19:58] (Version actuelle) gbdivers créée |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | ^ Chapitre précédent ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ Chapitre suivant ^ | + | <note warning>Ce cours n'est plus à jour, il est préférable de ne pas le suivre. Je vous recommande le cours sur Zeste de Savoir : https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/.</note> |
- | + | ||
- | __ const correcteness des fonction membres __ | + | |
- | + | ||
- | ====== Créer des classes ====== | + | |
- | + | ||
- | __ rappel sur la sémantique de valeur, à quoi cela correspond __ | + | |
- | + | ||
- | Deux aspects : structurer les données et leur appliquer des traitements. Dans les chapitres précédents, vu la partie traitement : l'algorithmique. La POO vise à fournir une méthode pour structurer les données. | + | |
- | + | ||
- | ===== Classe et objet ===== | + | |
- | + | ||
- | Classe = type définit dans le code, objet = ce qui apparaît en mémoire dans le code. objet est l'instanciation d'une classe. Il peut y avoir plusieurs objets qui sont instanciés à partir d'une classe . | + | |
- | + | ||
- | La syntaxe, pour une classe qui ne fait rien (vide) : | + | |
- | + | ||
- | <code cpp> | + | |
- | class A { // une classe | + | |
- | }; | + | |
- | + | ||
- | A a1; // un premier objet, qui s'appelle "a1" et de type "A" | + | |
- | A a2; // un second objet, qui s'appelle "a2" et de type "A" | + | |
- | </code> | + | |
- | + | ||
- | (attention au point-virgule après les crochets). Il est possible d'utiliser également le mot clé ''struct'' (la différence sera expliquée ensuite). Dans les 2 cas, cela permet de créer une classe | + | |
- | + | ||
- | <code cpp> | + | |
- | struct B { | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | On peut avoir plusieurs objets instanciés à partir d'une même classe, mais on ne peut pas avoir 2 classes avec le même nom (tout comme on ne pouvait pas avoir 2 variables avec le même nom). | + | |
- | + | ||
- | En pratique, une classe est un type. Par exemple, vous avez déjà vu des exemples de classe : string, vector, array. Leur utilisation est identique à n'importe quel type de la bibliothèque standard. | + | |
- | + | ||
- | Le nom des classes suit les mêmes règles (caractères autorisés) que les noms de variables ou de fonction. | + | |
- | + | ||
- | <note info> | + | |
- | Il est classique lors que l'on écrit des codes d'exemples de donner des noms bateaux aux noms. Vous avez déjà vu par exemple i, j, k pour nommer des entiers, s ou str pour des chaînes, f(), g(), h(), foo(), bar() pour des fonctions. Pour les classes, on donne souvent une lettre majuscule : A, B, C, etc. | + | |
- | + | ||
- | Ces choix de noms ne sont bien sûr pas à utiliser dans un vrai projet, uniquement pour des exemples ou poser une question sur un forum. | + | |
- | </note> | + | |
- | + | ||
- | Comme il n'y a pas de différence entre les classes de la lib standard et vos propres classes, vous pouvez les utiliser n'importe où, comme vous le faisiez pour les autres types. | + | |
- | + | ||
- | <code> | + | |
- | class A {}; // pour définir un type (A est une classe) | + | |
- | + | ||
- | A a {}; // pour définir une variable (a est un objet) | + | |
- | void f(A a) {} // comme paramètre de fonction | + | |
- | A g() {} // comme paramètre de retour de fonction | + | |
- | + | ||
- | f(a); // comme argument de fonction | + | |
- | a = g(); // comme résultat de fonction | + | |
- | auto a = f(); // avec auto | + | |
- | + | ||
- | template<typename T> | + | |
- | void h(T t) {} // fonction template | + | |
- | + | ||
- | h<A>(); // comme argument template de fonction | + | |
- | std::vector<A> v; // comme argument template d'une classe | + | |
- | </code> | + | |
- | + | ||
- | ===== Les membres d'une classe ===== | + | |
- | + | ||
- | Une classe rassemble des variables et des fonctions. S'appellent variables membres ou attributs et fonctions membres ou méthodes. Déclare membres de la même façon que d'habitude : | + | |
- | + | ||
- | <code cpp> | + | |
- | struct A { | + | |
- | + | ||
- | int i {}; | + | |
- | + | ||
- | int f() { | + | |
- | cout << "appel de f()" << endl; | + | |
- | } | + | |
- | + | ||
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Pour les appeler, il faut créer un objet puis utiliser ses membres (comme vous l'avez fait pour les classes de la lib standard) : | + | |
- | + | ||
- | <code cpp> | + | |
- | A a {}; // un objet de type A | + | |
- | + | ||
- | a.i = 123; // modification de i | + | |
- | cout << a.i << endl; // utilisation de i | + | |
- | + | ||
- | a.f(); // utilisation de f() | + | |
- | </code> | + | |
- | + | ||
- | Les membres d'une classe ne sont pas au même niveau (on parle de portée) que les fonctions libres, il n'y a pas de conflit entre les noms et ils est donc possible de donner le même nom à une fonction et une fonction membre. | + | |
- | + | ||
- | <code cpp> | + | |
- | void f() {} // fonction libre | + | |
- | + | ||
- | struct A { | + | |
- | f() {} // fonction membre | + | |
- | }; | + | |
- | + | ||
- | int main() { | + | |
- | f(); // appel de la fonction libre | + | |
- | + | ||
- | A a {}; | + | |
- | a.f(); // appel de la fonction membre | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Vous avez déjà rencontré cette situation avec les fonctions begin et end par exemple. Pour rappel, ces fonctions peuvent s'appeler comme des fonctions libres ou des fonctions membres : | + | |
- | + | ||
- | <code cpp> | + | |
- | vector<int> v {}; | + | |
- | + | ||
- | begin(v); // fonction libre | + | |
- | v.begin(); // fonction membre | + | |
- | </code> | + | |
- | + | ||
- | Pour déclarer de telles fonctions, il faut simplement créer une fonction membre et une fonction libre qui prend un paramètre : | + | |
- | + | ||
- | <code cpp> | + | |
- | struct A { | + | |
- | void f() { ... } // la fonction membre | + | |
- | }; | + | |
- | + | ||
- | void f(A const& a) { // on utilise une référence sur l'objet, pour ne pas le copier | + | |
- | a.f(); // on appelle la fonction membre | + | |
- | } | + | |
- | + | ||
- | A a {}; | + | |
- | a.f(); | + | |
- | f(a); | + | |
- | </code> | + | |
- | + | ||
- | Les deux fonctions font la même chose. | + | |
- | + | ||
- | Définitions : | + | |
- | + | ||
- | * déclaration : on dit au compilateur qu'un identifiant existe (A, i, etc) | + | |
- | * définition : on dit au compilateur à quoi correspond à un identifiant | + | |
- | * implémentation : on dit au compilateur comment on fait | + | |
- | + | ||
- | Exemple : | + | |
- | + | ||
- | <code cpp> | + | |
- | void f(); // déclaration (on dit que "f" existe) et définition (on dit que "f" est | + | |
- | // une fonction, qui ne prend aucun paramètre et retourne rien | + | |
- | + | ||
- | void f() { ... } // implémentation | + | |
- | + | ||
- | class A; // déclaration de A | + | |
- | + | ||
- | class A { // définition de A | + | |
- | int i {}; | + | |
- | void f(); | + | |
- | }; | + | |
- | + | ||
- | void A::f() { // implémentation de A::f | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | <note>''Type incomplet'' | + | |
- | + | ||
- | Un type est complet lorsqu'il est entièrement définie. Un type qui est simplement déclaré et pas encore définie est incomplet par exemple. Pour les fonctions et classes, on a vu comment séparer déclaration et définition. | + | |
- | + | ||
- | Pour une variable, possible aussi de séparer, en utilisant le mot-clé "extern" (cf ailleurs). Utile avec des libs. | + | |
- | + | ||
- | <code cpp> | + | |
- | extern int a; // déclaration | + | |
- | + | ||
- | a += 1; // erreur, a est simplement déclaré (ie le compilateur sait que l'identifiant "a" | + | |
- | // existe, mais il ne sait pas à quoi cela correspond) | + | |
- | + | ||
- | int a {}; // ok, définition | + | |
- | </code> | + | |
- | </note> | + | |
- | + | ||
- | ODR (one definition rule) : on peut avoir plusieurs déclarations, mais une seule définition __ header guard : éviter plusieurs définitions d'une même classe __ | + | |
- | + | ||
- | <code cpp> | + | |
- | class A {}; | + | |
- | class A {}; // erreur, double définition | + | |
- | + | ||
- | + | ||
- | class B; | + | |
- | class B; // ok, double déclaration | + | |
- | </code> | + | |
- | + | ||
- | Par contre, avant d'utiliser il faut que cela soit définie : | + | |
- | + | ||
- | <code cpp> | + | |
- | class A; | + | |
- | class A; // ok, double déclaration | + | |
- | + | ||
- | A a {}; // erreur, non défini | + | |
- | + | ||
- | class A {}; | + | |
- | A a {}; // ok, défini | + | |
- | </code> | + | |
- | + | ||
- | + | ||
- | déclaration anticipée | + | |
- | + | ||
- | <code cpp> | + | |
- | class A { | + | |
- | B b {}; // erreur, B n'est pas connu à ce niveau | + | |
- | }; | + | |
- | + | ||
- | class B { | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | <code cpp> | + | |
- | class B; // déclaration anticipée de B | + | |
- | + | ||
- | class A { | + | |
- | B b {}; // ok, le compilateur sait que B existe (même | + | |
- | // s'il ne sait pas à quoi cela correspond | + | |
- | }; | + | |
- | + | ||
- | class B { | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | __problématique de double inclusion de classe dans plusieurs fichiers __ | + | |
- | + | ||
- | + | ||
- | ===== Visibilité des membres d'une classe ===== | + | |
- | + | ||
- | 3 types de visibilité : | + | |
- | + | ||
- | * public (publique) : le membre est visible depuis l'extérieur de la classe ; | + | |
- | * private (privé) : le membre est n'est pas visible depuis l'extérieur de la classe ; | + | |
- | * protected (protégé) : le membre est visible que depuis les classes dérivées. | + | |
- | + | ||
- | Le troisième cas est lié à la notion d'héritage, sera vu dans les classes à sémantique d'entité. | + | |
- | + | ||
- | Par exemple, une classe A avec un membre f() ou i. Pour appeler le membre, on utilise l'opérateur . (comme déjà fait avec begin et end par exemple) : | + | |
- | + | ||
- | <code> | + | |
- | A a {}; // défini a | + | |
- | a.i = 0; // accès à membre i | + | |
- | a.f(); // accès à membre f() | + | |
- | </code> | + | |
- | + | ||
- | Bien faire attention aux notions de variables et types. A est un type, il permet de déclarer une variable. a est une variable, on peut appeler . dessus. | + | |
- | + | ||
- | Dans ce code, on utilise la classe A, on est l'extérieur de la classe. Comme on a accès aux membres, on a donc un accès publique. Avec un membre privé, essayer d'accéder depuis l'extérieur produit une erreur du compilateur : | + | |
- | + | ||
- | <code> | + | |
- | main.cpp:9:7: error: 'i' is a private member of 'A' | + | |
- | a.i = 123; | + | |
- | ^ | + | |
- | main.cpp:2:9: note: implicitly declared private here | + | |
- | int i {}; | + | |
- | ^ | + | |
- | main.cpp:10:7: error: 'f' is a private member of 'A' | + | |
- | a.f(); | + | |
- | ^ | + | |
- | main.cpp:3:10: note: implicitly declared private here | + | |
- | void f() {} | + | |
- | ^ | + | |
- | 2 errors generated. | + | |
- | </code> | + | |
- | + | ||
- | On défini la visibilité des membres en utilisant les mots-clés public, private et protected. Par exemple : | + | |
- | + | ||
- | <code> | + | |
- | class A { | + | |
- | public: | + | |
- | int i {}; | + | |
- | void foo() {} | + | |
- | private: | + | |
- | int j {}; | + | |
- | void g() {} | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | La déclaration de visibilité s'applique tant qu'un autre identificateur n'est pas spécifié. Donc dans ce code, i et f sont publique et j et g sont privés. | + | |
- | + | ||
- | <code> | + | |
- | A a {}; | + | |
- | a.i = 123; // ok | + | |
- | a.f(); // ok | + | |
- | a.j = 123; // erreur | + | |
- | a.g(); // erreur | + | |
- | </code> | + | |
- | + | ||
- | Par défaut, le mot-clé ''class'' crée une classe avec des membres en visibilité privée, on peut omettre le private s'il est en premier. Donc écrire : | + | |
- | + | ||
- | <code> | + | |
- | class A { | + | |
- | private: | + | |
- | int i {}; | + | |
- | void foo() {} | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | est équivalent à : | + | |
- | + | ||
- | <code> | + | |
- | class A { | + | |
- | int i {}; | + | |
- | void foo() {} | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Le mot-clé struct est similaire à class et permet de définir une classe. La seule différence est que struct a une visibilité publique par défaut : | + | |
- | + | ||
- | <code> | + | |
- | class A { | + | |
- | int i {}; | + | |
- | }; | + | |
- | + | ||
- | struct B { | + | |
- | int i {}; | + | |
- | }; | + | |
- | + | ||
- | A a {}; | + | |
- | a.i = 123; // erreur, i est privé avec class si public n'est pas spécifié | + | |
- | + | ||
- | + | ||
- | B b {}; | + | |
- | b.i = 123; // ok, i est publique avec struct par défaut | + | |
- | </code> | + | |
- | + | ||
- | <note>''Accesseurs'' | + | |
- | + | ||
- | Des accesseurs sont des fonctions membres spécifiques, qui permettent de lire et modifier des variables membres. Cela permet de mettre les variables membres en private et controler l'accès aux variables. | + | |
- | + | ||
- | <code cpp> | + | |
- | // sans accesseurs | + | |
- | class A { | + | |
- | public: | + | |
- | int m_i {}; | + | |
- | }; | + | |
- | + | ||
- | // avec acesseurs | + | |
- | class B { | + | |
- | private: | + | |
- | int m_i {}; | + | |
- | public: | + | |
- | int getI() const { return m_i } | + | |
- | void setI(int i) { m_i = i; } | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | + | ||
- | On les appelle souvent "getter" et "setter". | + | |
- | + | ||
- | A première vue, semble respecter encapsulation, mais vision des classes comme ensemble de données et pas comme un prestataire de services. Analogie : si on avait un classe Portefeuille, première approche correspond à "voila mon portefeuille, sers toi", la seconde à "voila X euros". La seconde est beaucoup plus sécurisée... | + | |
- | </note> | + | |
- | + | ||
- | ===== portée, statique et espace de noms ===== | + | |
- | + | ||
- | On peut remarquer que l'on a des syntaxes similaires. Un seul identifiant par portée. Un récapitulatif : | + | |
- | + | ||
- | <code cpp> | + | |
- | // déclaration fonction | + | |
- | void f(); // fonction libre dans la portée globale :: | + | |
- | + | ||
- | namespace myspace { | + | |
- | void f(); // fonction libre dans la portée myspace | + | |
- | } | + | |
- | + | ||
- | class A { | + | |
- | void f(); // fonction membre | + | |
- | static g(); // fonction membre static | + | |
- | }; | + | |
- | + | ||
- | + | ||
- | // utilisation | + | |
- | + | ||
- | int main() { | + | |
- | f(); // fonction libre de :: | + | |
- | ::f(); // autre syntaxe, avec portée globale explicite | + | |
- | + | ||
- | myspace::f(); // libre de myspace | + | |
- | + | ||
- | A a {}; | + | |
- | a.f(); // membre non statique | + | |
- | + | ||
- | A::g(); // membre static | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Portée : espace de noms (global ou user), classe, fonction dans lequel une identifiant est défini. Utilisation de l'opérateur de portée :: | + | |
- | + | ||
- | **Namelookup et signature de fonction** | + | |
- | + | ||
- | Quand on rencontre l'utlisation d'un identifiant, comment trouver la fonction qui correspond ? Template, spécialisation template, surcharge, mutliple définition (qu'est ce qui rentre dans la signature ?) | + | |
- | + | ||
- | + | ||
- | + | ||
- | ===== Encapsulation ===== | + | |
- | + | ||
- | On a vu que les classes déclarées que vous déclarez sont identiques à celle de la lib standard. La réciproque est vraie : les classes de la lib standard sont identiques au code que vous pouvez écrire. Elles sont écrites en C++, avec la même syntaxe que vous utiliser (des exercices à la fin du cours proposent de réécrire ces classes). | + | |
- | + | ||
- | En particulier, il est tout à fait possible d'aller regarder le code C++ des classes et fonctions de la lib standard pour voir comment elles sont implémentées. | + | |
- | + | ||
- | Mais la question importante est : avez-vous eu besoin de connaître le code de ces classes et fonctions pour les utiliser ? | + | |
- | + | ||
- | La réponse est bien sûr non (heureusement, comme vous les utiliser depuis le début du cours, si vous ne pouviez pas les utiliser sans voir leur code, vous seriez un peu bloqué). Pour utiliser une classe et une fonction, vous avez simplement besoin : | + | |
- | + | ||
- | * de connaître son nom et ce qu'elle fait ; | + | |
- | * et de connaître la liste de ses paramètres. | + | |
- | + | ||
- | Si une classe ou fonction est correctement conçue, il est généralement possible de savoir ce qu'elle fait et le rôle de chaque paramètre, rien qu'avec leur nom. Si on regarde par exemple la fonction [[http://en.cppreference.com/w/cpp/algorithm/sort|std::sort]] : | + | |
- | + | ||
- | <code> | + | |
- | template< class RandomIt > | + | |
- | void sort( RandomIt first, RandomIt last ); | + | |
- | </code> | + | |
- | + | ||
- | Le nom signifie ''tri'', on comprend qu'elle permet de trier quelque chose. Cette fonction prend deux paramètres, de type ''RandomIt'', qui s'appelle ''first'' et ''last''. On comprend donc que cette fonction permet de trier une collection entre un premier élément et un dernier. | + | |
- | + | ||
- | Pour ''RandomIt'', il faut bien sûr savoir ce qu'est un [[http://en.cppreference.com/w/cpp/concept/RandomAccessIterator|RandomAccessIterator]] pour savoir à quoi cela correspond. Mais (normalement) vous savez que cela correspond à des itérateurs, par exemple dans vector. | + | |
- | + | ||
- | L'ensemble des informations que l'on donne sur une classe ou une fonction et qui permet de les utiliser s'appelle l'interface d'une classe. Celle-ci contient les noms des classes, fonctions et paramètres que les utilisateurs peuvent utiliser, ainsi que la documentation. | + | |
- | + | ||
- | En pratique, cela signifie que pour utiliser une classe, vous pouvez écrire : | + | |
- | + | ||
- | __ doivent déjà connaître la différence entre définition et implémentation ? dans le chapitre sur les fonctions ? __ | + | |
- | + | ||
- | <code cpp> | + | |
- | void f(); // définition d'une fonction libre | + | |
- | + | ||
- | struct A { | + | |
- | int f(); // définition d'une fonction membre | + | |
- | + | ||
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Le code de la fonction n'est pas nécessaire pour comprendre comment utiliser cette classe. Le code permettant d s'appelle la définition (d'une classe ou d'une fonction). Le bloc de code {} est remplacé par '';'' | + | |
- | + | ||
- | Bien sûr, un moment donné, il faut donner le code correspond à la fonction. Ce code s'appelle l'implémentation de la fonction. Pour implémenter une fonction membre, il faut indiquer la classe correspondante, en utilisant l'opérateur de portée ''::'' : | + | |
- | + | ||
- | <code cpp> | + | |
- | // implémentation d'une fonction libre | + | |
- | void f() { | + | |
- | ... | + | |
- | } | + | |
- | + | ||
- | // implémentation d'une fonction membre | + | |
- | void A::f() { | + | |
- | ... | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | On va même pouvoir aller plus loin et séparer les définitions et implémentations dans 2 fichiers séparés. Le premier dans .h et second dans .cpp. Compilation de .cpp et inclusion de .h | + | |
- | + | ||
- | Remarque : sauf template | + | |
- | + | ||
- | On peut remarquer un avantage très intéressant de cette séparation : on peut modifier l'implémentation, sans que cela impact la définition. Cela implique donc que l'on peut modifier le code interne d'une classe ou d'une fonction sans avoir besoin de modifier le code qui l'utilise. | + | |
- | + | ||
- | Pour être concret, si on écrit une fonction d'incrémentation, on pourra écrire : | + | |
- | + | ||
- | <code cpp> | + | |
- | template<typename T> | + | |
- | void next(T & value) { | + | |
- | value += 1; | + | |
- | } | + | |
- | + | ||
- | int i {}; | + | |
- | next(i); | + | |
- | + | ||
- | auto it = begin(v); | + | |
- | next(it); | + | |
- | </code> | + | |
- | + | ||
- | Par la suite, on réalise que ce code ne fonctionne pas avec les itérateurs de std::list (qui n'est pas un RandomIterator, mais un [[http://en.cppreference.com/w/cpp/concept/BidirectionalIterator|BidirectionalIterator]], qui ne propose pas l'opérateur += ). | + | |
- | + | ||
- | On peut alors corriger le code en utiliser l'opérateur ++ : | + | |
- | + | ||
- | <code cpp> | + | |
- | template<typename T> | + | |
- | void next(T & value) { | + | |
- | ++value; | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Le code précédent continue de fonctionner (le code est maintenable) et on peut à présent utiliser std::list (le code est évolutif). | + | |
- | + | ||
- | La séparation entre définition et implémentation s'appelle l'encapsulation. Ce principe permet de gagner en maintenabilité et évolutivité du code. Plus les classes et fonctions seront correctement encapsulées, pour votre code gagnera en qualité. | + | |
- | + | ||
- | Il ne sera pas toujours possible de séparer correctement définition et implémentation. Ce n'est pas grave, il faut juste être conscient que cela aura un impact sur la maintenabilité et l'évolutivité. | + | |
- | + | ||
- | Une erreur classique est d'exposer les détails interne d'une classe. Par exemple, si vous avez une classe qui contient un vector et que vous voulez pouvoir modifier les éléments de ce vector, vous pouvez écrire: | + | |
- | + | ||
- | <code cpp> | + | |
- | struct A { | + | |
- | vector<int> v {}; | + | |
- | }; | + | |
- | + | ||
- | // ou équivalent, un accesseur : | + | |
- | + | ||
- | class B { | + | |
- | vector<int> v {}; | + | |
- | public: | + | |
- | vector<int> & get_v { return v; } | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | On expose vector vers l'extérieur, il fait partie de l'interface. Si un jour on utiliser un std::list, le code utilisateur ne sera pas forcement correct | + | |
- | + | ||
- | Possible d'éviter cela en ne mettant pas vector en interface. Par exemple, on peut proposer des fonctions begin et end, comme pour les conteneurs (il faut fournir les versions const et non const. __ Pourquoi ? Comment savoir ce qu'il faut fournir ? __) : | + | |
- | + | ||
- | <code cpp> | + | |
- | class A { | + | |
- | Container v {}; | + | |
- | public: | + | |
- | using container = vector<int>; | + | |
- | using iterator = container ::iterator; | + | |
- | using const_iterator = container ::iterator; | + | |
- | + | ||
- | iterator begin() { return begin(v); } | + | |
- | const_iterator begin() const { return begin(v); } | + | |
- | iterator end() { return end(v); } | + | |
- | const_iterator end() const { return end(v); } | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Autre solution, design pattern visiteur, ie prendre une fonction à appliquer sur chaque élément : | + | |
- | + | ||
- | <code cpp> | + | |
- | class A { | + | |
- | vector<int> v {}; | + | |
- | public: | + | |
- | template<typename Function> | + | |
- | void apply(Function f) { | + | |
- | std::for_each(begin(v), end(v), f); | + | |
- | } | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | ===== Principe de responsabilité unique ===== | + | |
- | + | ||
- | Faire une seule chose et le faire bien | + | |
- | + | ||
- | ===== Classe template ===== | + | |
- | + | ||
- | De la même manière que l'on a pu définir des fonctions template, il est possible de définir des classes template. Un exemple de classe template, c'est vector ou array. | + | |
- | + | ||
- | La déclaration d'un classe template est similaire à une fonction, il faut ajouter template avec la liste des paramètres template : | + | |
- | + | ||
- | <code> | + | |
- | template<typename T> | + | |
- | class A { | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Les paramètres template peuvent être utilisé dans l'ensemble de la classe, pour la déclaration d'une variable membre ou comme paramètre de fonction membre. | + | |
- | + | ||
- | <code> | + | |
- | template<typename T> | + | |
- | struct A { | + | |
- | T value {}; | + | |
- | T f(T param); | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | __ séparation implémentation et définition __ | + | |
- | + | ||
- | Pour instancier une classe template, il faut spécifier les arguments template. Contrairement aux fonctions template, le compilateur ne peut pas déduire les arguments template à partir des arguments de fonction. | + | |
- | + | ||
- | <code> | + | |
- | A<int> a_int {}; | + | |
- | A<double> a_double {}; | + | |
- | </code> | + | |
- | + | ||
- | ^ Chapitre précédent ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ Chapitre suivant ^ | + | |
- | + | ||
- | {{tag> Cours C++}} | + |