Cours C++ de base Cours C++ de base
Transcription
Cours C++ de base Cours C++ de base
Cours C++ de base Cours C++ de base 1. Introduction 2. Programmation procédurale et Standard Template Library (STL) 3. La classe C++ comme type abstrait Jean-Paul Rigault 4. Héritage et programmation par objets Polytech’Nice Sophia Département de sciences informatiques Université de Nice Sophia Antipolis 5. Résumé des mécanismes fondamentaux de C++ Email: [email protected] URL: http://users.polytech.unice.fr/~jpr C++ de base 1 ©2013 — Jean-Paul Rigault 2 C++ de base ©2013 — Jean-Paul Rigault Introduction Cours C++ de base De C à C++ Exemple de programme partie 1 Introduction à la classe C++ Introduction C++ de base 3 ©2013 — Jean-Paul Rigault C++ de base 4 ©2013 — Jean-Paul Rigault De C à C++ Avantages et inconvénients de C Inconvénients Typage laxiste Langage incomplet : manque d’instructions pour Avantages Langage de à la fois haut et bas niveau Types définissables par le programmeur Compilation séparée Portabilité Compilateurs efficaces Bien adapté à la programmationsystème • entrées-sorties • programmation concurrente • traitement de chaines de caractères • nombres complexes, matrices… Langage non extensible • sauf par bibliothèques Approche démodée de la modularité ©2013 — Jean-Paul Rigault 5 C++ de base De C à C++ Un langage « multi-paradigmes » De C à C++ Une brève histoire de C++ 1960 1970 1980 Fortran 1990 Ada 83 Algol 60 2000 BCPL 2010 Ada 95 // Fichier hello.cpp ANSI C C ISO C++ Standard C++ C++ARM C++98 C++03 main() est un exemple de fonction libre (non membre de classe) Les éléments de la bibliothèque standard sont dans le namespace std C++11 C++14 C++17 int main() { ML CLU Eiffel Simula 67 Objective C C# D ! std::cout << "hello, world" << std::endl; ! return 0; } Java Smalltalk C++ de base Importe les interfaces (bibliothèque standard, autres...) 2020 #include <iostream> C with classes Lisp ©2013 — Jean-Paul Rigault 6 C++ de base Exemple de programme simple en C++ (1) Algol 68 CPL Programmation par objets Hiérarchie des types d’objets (taxonomie, héritage) Liaison dynamique des opérations en fonctions du type réel des objets (« méthodes », « fonctions virtuelles ») Programmation générique Paramétrage des fonctions et classes par des types Programmation fonctionnelle Manipulation des fonctions comme des données (programmation d’ordre supérieur) Les algorithmes et objets-fonctions de la STL Programmation procédurale (ou impérative) Structuration du flot de contrôle Riche collection de structures de données et algorithmes (STL) Modularité Groupement données/fonctions Encapsulation, contrôle d’accès, masquage d’information Séparation interface/corps Types abstraits Transforme les modules en types décrivant des abstractions du « monde réel » Possibilité d’instanciation Les classes iostreams et les E/S avec classes iostreams et les opérateurs opérateurs associés << (sortie) et >> associés << (sortie) et >> (entrée) (entrée) CLOS 7 ©2013 — Jean-Paul Rigault C++ de base 8 ©2013 — Jean-Paul Rigault Exemple de programme simple en C++ (2) Exemple de programme simple en C++ (3) Importe les noms connus du namespace std ... // Fichier hello.cpp Compilation d'un programme C++ avec gcc gcc version 4.7 ou 4.8 (pour la compatibilité C++ 2011) #include <iostream> … afin les utiliserde sans Lesde éléments la la qualification standard std:: bibliothèque using namespace std; cout << "hello, world" << endl; ! return 0; } compilation en mode C++ 2011 g++ -g -Wall -std=c++11 -o hello hello.cpp ... int main() { ! générer info de debug cout : sortie standard cin : entrée standard cerr : erreur standard endl : à la ligne et « flushe » le buffer Le cours utilise C++ 2011 using namespace std doit être évité dans les fichiers d'entête (*.h) Les propriétés spécifiques à C++ 2011 sont indiquées par une bande verticale verte le long de la marge gauche des diapos ©2013 — Jean-Paul Rigault 9 C++ de base nom du binaire exécutable (défaut: a.out) tous warnings actifs compilateur/ éditeur de liens Introduction à la classe C++ Classe Stack (1) Introduction à la classe C++ Définition d'une pile (Stack) gardes d'inclusion unique : indispensables en C++ #ifndef _STACK_H_ #define _STACK_H_ _tab Pile de réels double 0 1 2 3 LIFO : Last In First Out Vue abstraite d'une pile: quatre opérations 2.71828 3.14159 6.02E+23 9.81 _top . . . push : ajouter un élément pop : retirer le dernier élement is_full : pile pleine ? // Fichier Stack.h : definition de la classe Stack const size_t N = 10;!! // taille de la pile private: pop() push() ! double _tab[N];!! // éléments ! size_t _top = 0;! // indice du sommet ©2013 — Jean-Paul Rigault constructeur ! Stack(); ! void push(double x); ! double pop(); ! bool is_full() const; ! bool is_empty() const; #endif // _STACK_H_ 11 les tableaux de C sont dépréciés en C++ public: }; C++ de base étiquette de contrôle d'accès class Stack { N-1 is_empty : pile vide ? ©2013 — Jean-Paul Rigault 10 C++ de base C++ de base fonctions-membres ATTENTION ! 12 ces fonctions ne modifient pas leur argument implicite ©2013 — Jean-Paul Rigault Introduction à la classe C++ Classe Stack (2) Introduction à la classe C++ Classe Stack (3) // Fichier Stack.cpp : implémentation de Stack // Fichier tst_Stack.cpp : utilisation de Stack #include <iostream> #include "Stack.h" #include "Stack.h" Stack::Stack() {}! using namespace std; void Stack::push(double x) {_tab[_top++] = x;} Alloue et initialise une nouvelle Stack (variable locale automatique) int main() { double Stack::pop() {return _tab[--_top];} double x; bool Stack::is_full() const {return _top >= N;} Stack st;! Tant que l'on arrive à lire un double : arrêt sur fin de fichier (EOF) ou erreur d'E/S while (cin >> x) bool Stack::is_empty() const {return _top == 0;} st.push(x); while (not st.is_empty()) Ici les fonctions sont tellement simples qu'on aurait pu les mettre directement dans la définition de la classe Stack (dans Stack.h) cout << st.pop() << ' '; cout << endl; return 0; } 13 C++ de base ©2013 — Jean-Paul Rigault Introduction à la classe C++ Classe Stack (4) Compilation et exécution ©2013 — Jean-Paul Rigault Introduction à la classe C++ Classe Stack générique (1) Toutes les piles se ressemblent meroe% g++ -g -Wall -std=c++11 -o tst_Stack tst_Stack.cpp Stack.cpp meroe% ./tst_Stack Fin de fichier au terminal (Unix/Linux) : Control-D seul sur une ligne 1 2 3 4 5 ^D 5 4 3 2 1 Elles ont pratiquement le même code… … mais diffèrent par le type des éléments qu'elles contiennent leur taille (ici une constante entière de compilation) Pourquoi ne pas prendre en paramètres ces deux entités ? meroe% C++ de base 14 C++ de base 15 ©2013 — Jean-Paul Rigault C++ de base 16 ©2013 — Jean-Paul Rigault Introduction à la classe C++ Classe Stack générique (2) #ifndef _STACK_H_ #define _STACK_H_ // Fichier Stack.h : definition de la classe générique template <typename T, size_t N> class Stack { private: ! ! T _tab[N];! ! // éléments size_t _top = 0;! // indice du sommet Introduction à la classe C++ Classe Stack générique (3) // Fichier tst_Stack.cpp : utilisation de Stack générique #include <iostream> #include <string> Stack T est un (a priori) n'importe quel type, N une constante entière dont la valeur est connue à la compilation int main() { ! double x; public: ! Stack() {} ! ! ! void push(T x) {_tab[_top++] = x;} ! ! ! ! T pop() {return _tab[--_top];} bool is_full() const {return _top >= N;} ! ! while (not st.is_empty()) ! cout << st.pop() << ' '; ! }; bool is_empty() const {return _top == 0;} ! cout << endl; ! Stack<string, 100> sts; ! ! sts.push("hello"); return 0; #endif _STACK_H_ Ici on a mis le corps des fonctions-membres directement dans la classe De toutes façons, pour les classes (ou fonctions) génériques on ne peut pas avoir une séparation interface/corps (.h/.cpp) 17 C++ de base ©2013 — Jean-Paul Rigault Stack<double, 10> st;! while (cin >> x) st.push(x); Définition de deux classes (normales) qui seront utilisées comme exceptions. Ces deux classes sont ici internes à Stack<...> (ce qui n'est pas obligatoire). Les classes sont vides : le plus important est leur nom ! class Stack { private: ! // inchangé public: Instanciation de la classe template Stack avec d'autres paramètres, ici std::string. } ©2013 — Jean-Paul Rigault 18 C++ de base Introduction à la classe C++ Exceptions (1) template <typename T, size_t N> Instanciation de la classe template Stack pour double. Le résultat est un vrai type généré par le compilateur, comme si on l'avait écrit directement #include "Stack.h" using namespace std; Introduction à la classe C++ Exceptions (2) int main() { ! double x; ! Stack<double, 10> st;! ! try { ! ! ! ! ! ! ! while (true) Block try : le comportement normal du programme while (cin >> x) ! class Full {}; ! class Empty {}; ! void push(T x) { ! ! ! ! ! if (is_full()) throw Full(): ! ! cout << endl; ! ! _tab[_top++] = x; ! } ! } ! catch (Stack<double, 10>::Full) { ! T pop() { ! ! ! ! if (is_empty()) throw Empty(); ! } ! ! return _tab[--_top]; ! catch (Stack<double, 10>::Empty) { ! } ! // autres fonctions inchangées Créationdede l'exception Création l'exception par par throw ; abandon de la throw ; abandon de la fonction courante envoi de l'exception fonctionetcourante et envoi à l'appelant de l'exception à l'appelant }; C++ de base ! ! ! } st.push(x); cout << st.pop() << ' '; Clauses Clausescatch catch::traitement traitementdes descas cas exceptionnels, exceptionnels,des deserreurs... erreurs... cerr << "Opération interdite sur pile pleine" << endl; cerr << "Opération interdite sur pile vide" << endl; } 19 ©2013 — Jean-Paul Rigault C++ de base 20 ©2013 — Jean-Paul Rigault Programmation procédurale et Standard Template Library (STL) Cours C++ de base Types, structures de données et de contrôle de C++ Structures et unions en C++ Expressions et structures de contrôle Fonctions • • Passage d'argument par référence Surcharge des opérateurs et fonctions La Standard Template Library partie 2 Containers et itérateurs Programmation procédurale et Standard Template Library (STL) C++ de base 21 ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Structures et unions Les structures et unions peuvent être utilisées de la même manière qu'en C Les unions sont peu fréquentes en C++ Les structures (struct) sont en fait des classe (voir Partie 3) Différence avec C typedef automatique des tags de struct, union et enum enum Color {BLUE, WHITE, RED}; Color col = RED; struct Address {unsigned _zip; string _city;}; Address addr = {06903, "Sophia Antipolis"}; • • • Containers de base : array, vector, et string Autres containers (list, dequeue, containers associatifs) Itérateurs • • • • Présentation et caractéristiques Lambdas-expressions de C++11 Panorama des algorithmes Algorithmes ou boucles explicites ? Algorithmes de la STL 23 ©2013 — Jean-Paul Rigault ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Expressions (1) Expressions Nouveaux opérateurs : uniquement unaires (new, delete, typeid, throw…) Les opérateurs logiques (||, &&, !, …) peuvent être remplacés par les plus lisibles or, and, not… Type d'une expression : même règle qu'en C • Le type d'une expression doit pouvoir être évalué par le compilateur à partir des seuls types des opérandes de l'expression, et ceci récursivement Auto-typage int a; double b; int c; int x = a + 2*(b - c); auto x = a + 2*(b - c); union Real_Int {int _int; double _double;}; Real_Int ri = 42; C++ de base 22 C++ de base C++ de base 24 int ? vraiment ? x prend le type de l'expression (double) ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Expressions (2) Pointeur nul Transtypage (casts) Traditionnellement, en C comme en C++ le pointeur nul (qui ne pointe sur rien) était traditionnellement représenté La syntaxe des casts de C est dépréciée en C++ Force la division réelle int a, b; double r = (double)a/b; à remplacer par • • soit double(a)/b (qui n'est pas toujours possible) soit static_cast<double>(a)/b C++ de base dynamic_cast : étudié avec l'héritage static_cast : très sûr pour forcer une conversion implicite reinterpret_cast : pour les programmeurs système ; hors sujet ici const_cast : oubliez-le ! 25 • • soit par la valeur entière 0 soit pas la macro du préprocesseur NULL (définie dans <cstdlib> comme étant 0) int *pi1 = 0; int *pi2 = NULL; Ceci posait des problèmes en cas de surcharge des fonctions Il existe 4 casts: • • • • Types, structures de données et de contrôle Expressions (3) ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Structures de contrôle void f(int); void f(char *pc); f(0); Quel f() ? C++ 2011 a introduit un vrai pointeur nul, avec le mot-clé nullptr f(nullptr); C++ de base f(char *pc) 26 ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Fonctions Différentes sortes de fonctions en C++ Fonction-membre : fonction définie à l'intérieur d'une classe et possédant un paramètre implicite du type de cette classe • Fonction libre : une fonction non membre de classe ; elle n'a pas de paramètre implicite Nouvelles structures de contrôle Gestion d"exceptions : bloc try/catch • • Nouvelle boucle for (voir plus loin) Toutes les autres structures de contrôle sont héritées de C 27 ©2013 — Jean-Paul Rigault exemple : main, std::printf… peut-être une fonction globale (namespace) ou un membre static de classe Opérateur : une fonction portant un nom de la forme operator! où ! est la notation d'un opérateur de C++ • • • C++ de base exemple : Stack::push, Stack::is_empty... C++ de base exemple : operator<<, operator+, operator+=… La plupart des opérateurs peuvent être définis soit comme des fonctionsmembres, soit comme des fonctions libres cependant certains opérateurs doivent être des fonctions-membres 28 ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Fonctions : passage d'arguments (1) Passage par référence : spécifique à C++ Par défaut, les arguments sont passés par valeur c'est-à-dire par copie du paramètre effectif dans une variable locale correspondant au paramètre formel struct Address { … }; void f(Address a) { … } Address addr; f(addr); ! Types, structures de données et de contrôle Fonctions : passage d'arguments (2) Notion de référence Alias, synonyme, autre nom pour un objet int i = 42; int& ri = i; ! ! assert(ri == 42);! ri++; assert(i == 43);! ! i++; assert(ri == 44);! ! addr copié dans variable a a détruite à la fin de f() Ils peuvent être passés par adresse (pointeurs) soit pour éviter la copie soit pour permettre la modification du paramètre effectif dans le corps de la fonction void move(Address *addr, const Address *newaddr) { ! // … details omis … *addr modifiable dans f() ! *addr = *newaddr; *newaddr constant dans f() } C++ de base ©2013 — Jean-Paul Rigault 29 Types, structures de données et de contrôle Fonctions : passage d'arguments (3) Utilisations analogues au passage par pointeurs… void move(Address& addr, const Address& newaddr) { ! // … details omis … ! addr = newaddr; } … mais sans la pollutions syntaxique (*, &) void swap(int& a, int& b) { ! int tmp = b; ! b = a; ! a = tmp; } void swap(int *a, int* b) { int tmp = *b; *b = *a; *a = tmp; } int x = 1, y = 2; swap(x, y); int x = 1, y = 2; swap(&x, &y); 31 assert(&i == &ri); ! Les références ne sont pas copiables int j = 17; int& rj = j; ri = rj;! ! ! Ici équivalent à : i = j; ! Une référence ne peut pas être nulle et doit toujours être initialisée C++ de base 30 ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Fonctions : passage d'arguments (4) Référence ou pointeur Passage d'arguments par référence C++ de base ri est une référence sur i ri et i sont 2 noms pour le même objet ©2013 — Jean-Paul Rigault Une référence peut remplacer un pointeur, à condition que le dit pointeur • soit toujours initialisé et jamais nul • ne change pas de valeur (pointe toujours sur le même objet Retour de fonction par référence Ne pose aucun problème, sinon que l'objet référencé doit exister après le retour de la fonction loc disparait à la fin de f() int& f() { ! int loc = 42; ! return loc; } int i = f(); C++ de base Que référence le retour de f() ? 32 ©2013 — Jean-Paul Rigault Types, structures de données et de contrôle Fonctions : surcharge Fonction template Surcharge des fonctions Plusieurs fonctions • • • de même nom d'implémentations différentes de liste de paramètres différentes (en nombre et/ou types) void Error(const string& msg, double); void Error(const string& msg, int); void Error(const string& msg, const string&); void Error(const string& msg); Le compilateur utilise les paramètres effectifs pour déterminer la version de la fonction à invoquer Le mécanisme est très compliqué à cause des conversions implicites • • il peut y avoir des ambigüités mais la conformance exacte de type gagne toujours 33 C++ de base Types, structures de données et de contrôle Fonctions : surcharge paramétrée (template) ©2013 — Jean-Paul Rigault Possibilité d'avoir des fonctions de même nom surchargées et dépendant d'un ou plusieurs paramètres génériques de type template <typename T> const T& min(const T& a, const T& b) { Pourquoi le retour par ! return a < b ? a : b; référence est-il sûr ici ? } int i = … ; double x = … ; std::string s1 = … ; i = min(i, 42); !! ! ! // min<int> x = min(3.141592, x * x); ! // min<double> std::string s2 = min(s1, "hello"); // min<string> Container de la STL Ensemble de classes et fonctions templates correspondant aux structures de données et les algorithmes fondamentaux de la programmation Toutes les opérations et structures sont spécifiées avec leur complexité en temps et en mémoire (notation O(n)) Les éléments de la STL sont tous dans le namespace std • Nous laissons tomber dans la suite la qualification std:: list<int> li; ➞ std::list<int> li; Documentation en ligne sur la STL Voir par exemple http://en.cppreference.com ou bien http://www.cplusplus.com/reference/ • C++ de base ou http://fr.cppreference.com pour ceux qui sont réfractaires à l'anglais (attention à la traduction qui a été faite automatiquement !) 35 ©2013 — Jean-Paul Rigault La Standard Template Library (STL) Panorama des containers (1) La Standard Template Library (STL) La Standard Template Library (STL) 34 C++ de base ©2013 — Jean-Paul Rigault Collection homogène d'objets (tous les objets de même type) Les objets peuvent être de type primitif ou classe Interface identique d'itération (traversée de la collection) Interface de manipulation du container sure du point de vue type Implémentation des containers Diverses implémentations selon les fonctionnalités et performances requises Sous interface commune à tous les containers Implémentation comme classes génériques (templates) Stratégie d'allocation mémoire paramétrable par un paramètre template (allocateur) C++ de base 36 ©2013 — Jean-Paul Rigault La Standard Template Library (STL) Panorama des containers (2) Containers de taille fixée à la compilation (N) pair<T1,T2> Collection de N = 2 éléments tuple<T1,T2, …,TN> Collection de N éléments array<T,N> Tableau de N éléments de type T (substitut d'un tableau C) ; itérable Séquences (préservent l'ordre relatif d'insertion) : itérables vector<T> Tableau consécutif en mémoire de taille variable dynamiquement (indexable) dequeue<T> Tableau de taille variable dynamiquement (indexable et insérable) list<T> Liste doublement chainée (facile à insérer, non indexable) string Chaine de caractères de taille variable dynamiquement (substitut de char *) Containers associatifs : itérables map<K, T> unordered_map<K,T> La Standard Template Library (STL) Panorama des containers (3) Adaptateurs de containers (caractérisés par leur stratégie d'accès) La manière de ranger les éléments dans le container est déléguée à un paramètre template qui doit être une séquence dont la valeur par défaut est indiquée ci-après. Ces containers sont non itérables. Pile : stratégie Last In First Out (LIFO) stack<T> Séquence par défaut : dequeue queue<T> File : stratégie First In First Out (FIFO) Séquence par défaut : dequeue priority_queue<T> L'insertion des éléments se fait de telle sorte que la file soit toujours ordonnée (par défaut selon l'operator< de T) Séquence par défaut : vector Tableau associatif de clé K et de valeur associée T sans duplication de K (versions arbre binaire et hash code) multimap<K, T> Tableau associatif de clé K et de valeur associée T avec duplication de K unordered_multimap<K, T> (versions arbre binaire et hash code) set<K> unordered_set<K> Ensemble sans répétition de K (versions arbre binaire et hash code) multiset<K> unordered_multiset<K> Ensemble avec répétition de K (versions arbre binaire et hash code) 37 C++ de base ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Modèle abstrait des itérateurs Containers et itérateurs de la STL Notion d'itérateur Liste doublement chainée avec sentinelles Sorte de « pointeur » (smart pointer) permettant de parcourir un à un les éléments d'un container Containers itérables rend() begin() array, séquences (y compris string) et containers associatifs iterator rbegin() end() it -++ Les adaptateurs de containers ne sont pas itérables *it Interface d'itération Tous les containers itérables proposent une interface d'itération commune Ils apparaissent pour leur itérateurs comme des listes doublement chainées • même s'il n'en est rien au niveau de l'implémentation C++ de base ©2013 — Jean-Paul Rigault 38 C++ de base 39 ©2013 — Jean-Paul Rigault list<int> L; for (int i = 0; i < N; i++) L.push_front(i); list<int>::const_iterator it; for (it = L.begin(); it != L.end(); ++it) ! cout << *it << ' '; C++ de base 40 L'itérateur dépend du type du container ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Itération sur l'ensemble d'un container (1) Containers et itérateurs de la STL Itération sur l'ensemble d'un container (2) Deuxième amélioration : range for loop L'opération consistant à parcourir tous les éléments d'un container avec un itérateur est très fréquente utilisable uniquement avec les bornes d'itération begin/end for (int e : v) { ! cout << e << ' '; } et a une syntaxe pénible... for (vector<int>::const_iterator it = v.begin(); it != v.end(); ++it) { ! cout << *it << ' '; } ou même for (auto e : v) { ! cout << e << ' '; } Si l'on veut modifier les éléments, il faut utiliser une référence Première amélioration for (int& e : v) { ! e = 0; } utilisable quelles que soient les bornes d'itération for (auto it = v.begin(); it != v.end(); ++it) { ! cout << *it << ' '; } 41 C++ de base ©2013 — Jean-Paul Rigault for (auto& e : v) { ! e = 0; } 42 C++ de base Containers et itérateurs de la STL Utilisation des itérateurs (1) ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Utilisation des itérateurs (2) list<int> li = {1, 1, 2, 2, 2, 3, 4, 5}; Utilisation directe vector<int> vi(li.begin(), li.end()); Dans une boucle (diapos précédentes) Comme paramètres ou résultats de certaines opérations Interval d'itération int n = 0; for (auto it = li.begin(); (it = find(it, li.end(), 2) != li.end()); Paire d'itérateurs (it1, it2) pointant tous les deux sur la même collection et tels que it2 est accessible depuis it1 (par applications répétées de l'opérateur ++) Désigne la (sous-)collection de tous les éléments compris entre celui pointé par it1 inclus et celui pointé par it2 exclus • on note souvent l'interval sous la forme [it1,it2[ ou [it1,it2) C++ de base Pour Pourtout touteedans dansvv... 43 ©2013 — Jean-Paul Rigault ++it) { ! ++n; } L'algorithme find() cherche (ici) le premier élément de valeur 2 dans la sous-liste correspondant à l'interval d'itération [it, li.end()[ il retourne un itérateur sur l'élément correspondant ou l'itérateur li.end() si la valeur n'est pas dans la liste C++ de base 44 ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers de taille fixe (1) Fonction utilitaire pour créer une paire à partir de 2 arguments de type (presque) quelconque Paire: pair<T1,T2> int i = 42; pair<int, string> p1 = make_pair(i, "hello"); ou encore pair<int, string> p1 = {i, "hello"}; Containers et itérateurs de la STL Containers de taille fixe (2) Extension de la paire : tuple<T1,T2, … ,TN> int i = 42; double x = 3.14; string s = "hello"; tuple<int, double, string> p = ! ! ! ! ! ! make_tuple(i, 3.14, "hello"); et pourquoi pas… cout << p.first << ' ' << p.second << endl; Noter que first et second ne sont pas des fonctions Utile pour regrouper deux éléments Utile aussi pour définir des fonctions retournant 2 valeurs 45 C++ de base ©2013 — Jean-Paul Rigault auto p = make_tuple(i, 3.14, "hello"); Les éléments sont désignés par leur rang (une constante de compilation) cout << get<0>(p) << ' ' << get<1>(p) << ' ' << get<2>(p) << endl; Containers et itérateurs de la STL Containers de taille fixe (3) Classe array<T, N>, substitut des tableaux de C Tableau de T de taille fixe N (connue à la compilation) Possède toutes les propriétés de vector, sauf celles modifiant la taille du tableau Itérable, copiable, possibles opérations de comparaison (lexicographiques) Taille consultable à l'exécution (fonction-membre size()) Substitut d'un tableau de C array<T, N> <array> Tableau de taille N (fixée à la compilation) 46 C++ de base ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers de taille fixe (4) Classe array<T, N> (suite) Éléments initialisés à 0.0 double tab[10]; array<double, 10> tab; size_t n = tab.size(); Retourne toujours N (ici 10) array<string, 3> plm = {{ ! "Paris", "Lyon", "Marseille", }}; Une seule { si option -Wno-missing-brace Pas de vérification d'indice size_t i = 3; tab[i] = 3.5; tab[147] = 0.0; tab.at(147) = 0.0; Exception std::out_of_range array<double, 10> tab2 = {{ 1.0, 2.0, 3.0, }}; tab = tab2; Copiededetableaux tableaux de même array<double, 10> tab3 = tab2; Copie de même type (et donctype même taille) C++ de base 47 ©2013 — Jean-Paul Rigault C++ de base 48 ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers de taille fixe (5) Containers et itérateurs de la STL Containers de taille variable (2) Classe array<T, N> : itération Légende array<double, 10> tab; Utilisation de l'indexation for (size_t i = 0; i < tab.size(); ++i) ! cout << tab[i] << ' '; for (size_t i = 0; i < tab.size(); ++i) ! tab[i] = 0.0; c, c1… une instance de container Types-membres for (auto it = tab.begin(); it != tab.end(); ++it) ! cout << *it << ' '; for (auto it = tab.begin(); it != tab.end(); ++it) ! *it = 0.0; Utilisation de la range for loop for (auto x : tab) ! cout << x << ' '; for (auto& x : tab) ! x = 0.0; 49 un type container (e.g., vector<double>, list<int>…) Interface commune des containers (séquences et containers associatifs) Utilisation d'un itérateur C++ de base Cont ©2013 — Jean-Paul Rigault Cont::value_type Le type des éléments du container Cont::iterator Cont::const_iterator Cont::reverse_iterator Cont::const_reverse_iterator ! value_type* ! const value_type* ! value_type* (++ avance à l'envers) ! const value_type* (++ avance à l'envers) Cont::reference Cont::const_reference ! value_type& ! const value_type& 50 C++ de base Containers et itérateurs de la STL Containers de taille variable (3) ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers de taille variable (4) Interface commune des containers (séquences et containers associatifs) Interface commune des containers (séquences et containers associatifs) Comparaison entre containers de même type Construction et destruction Cont c; Création du container vide Cont c = {e1,e2, …} Création d'un container à partir d'une liste d'initialisation Cont c1(c2); Création du container c1 comme copie de c2 Les deux containers doivent être de même type Cont c1(beg,end); Création du container c1 initialisé avec une copie des éléments de l'interval d'itération [beg,end[ Les deux containers ne sont pas nécessairement de même type Manipulation de la taille c.size() Nombre d'éléments (utiles) du container c.empty() Le container est-il vide ? c.max_size() Taille maximum du container permise par l'implémentation C++ de base 51 ©2013 — Jean-Paul Rigault c1 == c2 c1 != c2 Égalité et inégalité Le type de valeur doit lui-même avoir les opérateurs correspondants c1 c1 c1 c1 Comparaison « lexicographique » (i.e., comme les chaines de caractères) Le type de valeur doit lui-même avoir les opérateurs correspondants < c2 > c2 <= c2 >= c2 Affectations c1 = c2 Affectation entre containers de même type Le type de valeur doit être lui-même affectable Insertion et destruction d'éléments c.insert(pos,e) Insère l'élément e à la position (en général itérateur) pos L'interpretation de pos et la valeur retournée dépendent du type de container c.erase(beg,end) Enlève tous les éléments dans l'intervalle d'itération [beg,end[. Si end est absent, enlève seulement l'élément à la position beg. c.clear() Enlève tous les éléments (retour à l'état container vide) C++ de base 52 ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Séquences (1) Containers et itérateurs de la STL Séquences (2) Classes vector<T>, dequeue<T> et list<T> Préservent l'ordre relatif d'insertion vector<double> vec1; dequeue<string> dq1(10); list<int> li(10, 5); Insertion possible au début, au milieu et à la fin Effacement d'élément (simple ou multiple) Cas particulier: classe string … avec en plus des propriétés spécifiques aux chaines de caractères (concaténation, extraction de sous-chaines, recherche de caractères, etc.) <vector> <deque> <list> Tableau de taille variable dynamiquement Séquence avec indexation et insertion optimisées Liste doublement chainée string <string> Chaine de caractères (octets) taille 5 size_t n = vec2.size(); vec2 = {2,3,5,7,11,13} capitals = {"Saint Petersburg", "London","Paris"} ©2013 — Jean-Paul Rigault 53 Classe string Chaine vide (""), taille 0 string s0; string s1 = "hello"; string s2 = "world"; Conversion char * → string string msg; msg = s1 + ", " + s2 + "!"; cout << msg << endl; Concaténation de string et conversions implicites (msg == "hello, world!") … // vrai (msg[0] == 'h') … // vrai (msg[145] == 'h') … // indéfini (msg.at(145) == 'h') … // throw out_of_range Lit un mot (les espaces séparent les mots) string word; cin >> word; Lit l'entrée standard mot par mot et constitue une liste des mots ; arrêt sur fin de fichier ou erreur vector<string> vec; while (cin >> word) ! vec.push_back(word); 55 vec2 = {1,2,3,5,7,11,13} if (capitals.front() == "Moscow") … // vrai if (capitals.back() == "London") … // faux capitals.front() = "Saint Petersburg"; ©2013 — Jean-Paul Rigault 54 C++ de base Containers et itérateurs de la STL Séquences (3) C++ de base 10 éléments initialisés à 5 vec2.push_back(13); vec2.push_front(1); Séquences vector<T> deque<T> list<T> if if if if 10 éléments initialisés à 0 vector<double> vec2 = {2, 3, 5, 7, 11}; list<string> capitals = {"Moscow", "London", "Paris"}; Toutes les propriétés d'un vector<char> ... C++ de base Vecteur vide (taille 0) ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Séquences (4) Fonctions-membres spécifiques des séquences : construction V DQ L Cont c(n); Cont c(n, e); Création d'une séquence à n éléments, tous initialisés au « zéro » de leur type (resp. à e) ✔ ✔ ✔ c.capacity(); Retourne le nombre d"éléments effectivement alloués (est supérieur ou égal à size()) ✔ ✔ ✘ ✔ ✔ ✘ V DQ L ✔ ✔ ✔ V DQ L Reéserve la place pour au moins n éléments. Change la capacity() mais pas size(). Fonctions-membres spécifiques des séquences : affectation de valeur c.reserve(n); Assigne les n premiers éléments au « zéro » de leur type c.assign(n) c.assign(n, e); (resp. à e) Fonctions-membres spécifiques des séquences : accès aux éléments c[i] c.at(i) Retourne une référence sur l"élément d'indice i. La forme at() lance l'exception out_of_range en cas de débordement, ; aucune vérification pour []. ✔ ✔ ✘ c.front() c.back() Retourne une référence sur le premier (resp. le dernier) élément ✔ ✔ ✔ C++ de base 56 ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Séquences (5) Fonctions-membres spécifiques des séquences : changement de taille c.resize(n); c.resize(n,e); Change la taille de la séquence à valoir n. Les éventuels nouveaux éléments sont initialisés au « zéro » de leur type (resp. à e). Les éventuels éléments surnuméraires sont perdus. Containers et itérateurs de la STL Séquences (6) V DQ L Fonctions-membres spécifiques des listes (list<T>) ✔ ✔ ✔ c.unique(); c.unique(pred); Fonctions-membres spécifiques des séquences : insertion et destruction d'éléments c.insert(pos,n,e); Insère n éléments de valeur e avant la position définie par l'itérateur pos ✔ ✔ ✔ c.insert(pos,beg,end); Insère tous les éléments dans l'intervalle d'itération [beg,end[ avant la position définie par l'itérateur pos ✔ ✔ ✔ c.push_back(e); c.pop_back(); Insère e (resp. enlève l'élément) à la fin du container. Ne retournent rien. ✔ ✔ ✔ c.push_front(e); c.pop_front(); Insère e (resp. enlève l'élément) au début du container. Ne retournent rien. ✘ ✔ ✔ c.erase(pos); Retire l'élément à la position définie par l'itérateur pos. Ne retourne rien. ✔ ✔ ✔ 57 C++ de base ©2013 — Jean-Paul Rigault Insère tous les éléments de c2 (resp. de l'interval d'itération c1.splice(pos,c2); [beg,end[) devant la position définie par l'itérateur pos de c1.splice(pos, c2, beg, end); c1. Il s'agit d'un déplacement, rien n'est copié ! Les éléments déplacés ne figurent plus dans c2. c.sort(); c.sort(comp); Trie la liste sur place (resp. avec la fonction de comparaison comp()) c1.merge(c2); c1.merge(c2, comp); Fusionne les éléments de c1 dans la liste c2 (resp. avec la fonction de comparaison comp()). Les deux listes doivent être triées au départ (avec la même fonction de comparaison que celle utilisée ici). À la fin c2 est vide. c1.reverse(); Renverse la liste sur place. C++ de base Containers et itérateurs de la STL Adaptateurs de containers (1) Stack (FIFO), Queue (LIFO), Priority queue Priority queue utilise operator< par défaut et est triée en ordre décroissant Séquence sous-jacente par défaut : Stack et Queue : deque<T> Pile Last In First Out File First In First Out File à priorité (ordonnée dans ordre inverse) C++ de base 59 ©2013 — Jean-Paul Rigault S Q PQ push(e) Ajout d'un élément selon stratégie ✔ ✔ ✔ pop(e) Retire un élément selon stratégie (pas de retour) Retourne une référence sur le premier élément (à retirer) mais ne modifie pas le container Retourne une référence sur le premier élément (à retirer) mais ne modifie pas le container Retourne une référence sur le dernier élément (à retirer) mais ne modifie pas le container ✔ ✔ ✔ ✔ ✘ ✔ ✘ ✔ ✘ ✘ ✔ ✘ back() queue<T> <queue> priority_queue<T> <queue> ©2013 — Jean-Paul Rigault Fonctions-membres spécifiques : stack<T>, queue<T>, priority_queue<T> front() Adaptateurs de containers stack<T> <stack> 58 Containers et itérateurs de la STL Adaptateurs de containers (2) top() Priority queue : vector<T> Retire les éléments consécutifs égaux (resp. ceux pour lesquels le prédicat op(e) est vrai) C++ de base 60 ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers associatifs (1) Maps, multimaps, sets, et multisets Classes map<K,T> et unordered_map<K,T> 2 implémentations arbre binaire : recherche et insertion O(log N), container trié hachage : recherche et insertion « en moyenne » O(1), containers non trié Dans tous ces containers, les valeurs de la clé (K) sont constantes Pas de duplication des valeurs de K • Arbre binaire équilibre pour map : insertion et recherche en O(log Containers associatifs map<K,T> <map> Associe une valeur de K à une valeur de T multimap<K,T> <map> Associe une valeur de K à plusieurs valeurs de T set<K> <set> Ensemble sans répétition ordonné multiset<K> <set> Ensemble avec répétition (bag) <unordered_map> Associe une valeur de K à une valeur de T unordered_multimap<K,T> <unordered_map> Associe une valeur de K à plusieurs valeurs de T unordered_set<K> <unordered_set> Ensemble sans répétition unordered_map<K,T> <unordered_set> Ensemble avec répétition (bag) 61 C++ de base Associe un valeur de type T à une valeur de type K Implémentation Ces containers ne sont donc pas affectables unordered_multiset<K> Containers et itérateurs de la STL Containers associatifs (2) ©2013 — Jean-Paul Rigault • Les maps sont de collections de pair<const K, T> Les instances de ces classes peuvent être considérées comme des tableaux (dynamiques) de T indexés par K map<string, string> capitals = { Une map est une ! {"France", "Paris"}, collection de paires ! {"UK", "London"}, Affiche 3 ! {"USA", "Washington"}, }; L'opérateur [] cherche la clé dans la cout << capitals.size(); map ; s'il ne la trouve pas il crée une nouvelle paire, y copie la clé et initialise le cout << capitals["UK"]; second membre à son « zéro » (ici la chaine capitals["PRC"] = "Beijing"; vide ""). Dans tous les cas il retourne une référence sur ce second membre. cout << capitals["Japan"]; capitals.erase("Japan"); Affiche "" ; la taille est maintenant 5 Affiche "" ; la taille est 4 à nouveau cout << capitals.at("Japan"); C++ de base 63 throw out_of_range ©2013 — Jean-Paul Rigault 62 C++ de base Containers et itérateurs de la STL Containers associatifs (3) Classes map<K,T> et unordered_map<K,T> (suite) N), trié selon K Hachage pour unordered_map : insertion et recherche moyenne en O(1), non trié ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers associatifs (4) Classes multimap<K,T> et unordered_multimap<K,T> Associe un valeur de type T à une valeur de type K Duplication possible des valeurs de K Implémentation • Arbre binaire équilibre pour multimap : insertion et recherche en • O(log N), trié selon K Hachage pour unordered_multimap : insertion et recherche moyenne en O(1), non trié Les éléments dont la clé a la même valeur sont consécutifs Les multimaps sont de collections de pair<const K, T> Les multimaps sont des sortes de « dictionnaires » à définition multiple C++ de base 64 ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers associatifs (5) Classes multimap<K,T> et unordered_multimap<K,T> (suite) multimap<string, string> dico = { ! {"order", "disposition, sequence"}, ! {"order", "command"}, ! {"order", "peace, control"}, ! {"order", "class, category"}, !… }; Une multimap est une collection de paires Affiche 4 Pas d'indexation On peut considérer un set<K> comme une map<K,K> même si l'implémentation est différente (et même chose pour les 3 autres classes) Les formes unordered utilisent le hachage, les autres sont des arbres binaires • donc set<K> et multiset<K> sont ordonnés, les autres non Les formes unordered_ sont disponibles en C++11 L'insertion dans une multimap utilise les itérateurs 65 Classes set<K>, multiset<K>, unordered_set<K> et unordered_multiset<K> Les sets sont sans répétition, les multisets permettent la répétition (bags) cout << dico.count("order"); C++ de base Containers et itérateurs de la STL Containers associatifs (6) ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers associatifs (7) Constructeurs spécifiques des containers associatifs Cont c(op); Cont c(beg, end, op); 66 C++ de base ©2013 — Jean-Paul Rigault Containers et itérateurs de la STL Containers associatifs (8) Insertion et destruction dans les containers associatifs Crée un container associatif ordonné vide (resp. initialisé par les éléments de l'intervalle d'itération [beg,end[) et le prédicat op() comme critère de tri. Recherche dans les containers associatifs it = c.insert(k); p = c.insert(k); Insère un élément de clé k (dont la valeur associée est initialisée au « zéro » de son type. La première forme est pour les multimaps et multisets, ordonnés ou non : le retour est un itérateur it sur le nouvel élément. La deuxième forme est pour les maps et sets, ordonnés ou non : le retour est une paire p contenant un itérateur sur la nouvelle cellule et un booléen indiquant si effectivement un nouvel élément a été ajouté. c[k] c.at(k) Seulement pour les maps (ordonnées ou non). Cherche la clé k dans c et retourne une reférence sur le second élément de la paire. Si k n'est pas trouvé, l'opérateur [] crée une nouvelle cellule, copie k dedans , et initialise le second membre au « zéro » de son type alors que at() lance l'exception out_of_range. c.count(k); Nombre d'occurences de la clé k dans le container c c.insert(beg, end); it = find(k); Retourne un itérateur it sur le premier élément de clé k ou l'itérateur c.end() si la clé k n'est pas dans c. Inseère tous les éléments de l'intervalle d'itération [beg,end[. Ne retourne rien. c.erase(k); Retire tous les éléments de clé k. c.erase(pos); Retire l'élément à la position indiquée par l'itérateur pos. it0 = c.lower_bound(k); it1 = c.upper_bound(k); Retourne un itérateur sur le premier élément de clé k (resp. l'élément suivant le dernier de clé k) ; si k n'est pas trouvé, retourne c.end(). p = c.equal_range(k); Retourne la paire {it0,it1} définie ci-dessus. C++ de base 67 ©2013 — Jean-Paul Rigault C++ de base 68 ©2013 — Jean-Paul Rigault Algorithmes de la STL Notion d'algorithme Algorithmes de la STL Exemples d'algorithmes (1) Un algorithme est une fonction template qui applique une fonction à tous les éléments d'une collection afin d'obtenir un résultat global Les collections de travail sont définies par des intervals d'itération list<int> li = { … }; int n = count(li.begin(), li.end(), 3); if ((auto it = find(li.begin(), li.end(), n)) != li.end()) { // faire quelque chose si la liste contient n } Pour utiliser les algoritmes, inclure le fichier <algorithm> Filtrage d'une collection par une prédicat bool is_gt_0(double x) { ! return x > 0.0; } deque<double> dx = { … }; int n = count_if(dx.begin(), dx.end(), &is_gt_0); auto it = find_if(dx.begin(), dx.end(), &is_gt_0); assert(it == dx.end() or *it > 0.0); • Plus de 60 algorithmes prédéfinis C++ de base 69 ©2013 — Jean-Paul Rigault 70 C++ de base Algorithmes de la STL Exemples d'algorithmes (2) ©2013 — Jean-Paul Rigault Algorithmes de la STL Exemples d'algorithmes (3) Copie de collections de même type de valeur (suite) Copie de collections de même type de valeur deque<double> dx = { … }; vector<double> vec(dx.size()); Que faire si l'on ne connait pas a priori la taille du résultat ? Préallocation des éléments du résultat auto it = copy(dx.begin(), dx.end(), vec.begin()); it est un itérateur sur l'élément de vec suivant le dernier copié Aucun algorithme de la STL ne peut modifier la taille d'une collection bool is_gt_0(double x) { ! return x > 0.0; } deque<double> dx = { … }; vector<double> vec(dx.size()); Préallocation des éléments du résultat auto it = copy_if(dx.begin(), dx.end(), vec.begin(), &is_gt_0); Les deux collections vec et dx sont de même taille Le résultat vec est tel que les éléments de [it,vec.end()[ n'ont pas été copiés d'où ici la préallocation de vec • • en fait ils sont nuls (0.0) mais vec est plus long que nécessaire Solution : itérateurs d'insertion C++ de base 71 ©2013 — Jean-Paul Rigault C++ de base 72 ©2013 — Jean-Paul Rigault Algorithmes de la STL Exemples d'algorithmes (4) Copie de collections de même type de valeur : itérateurs d'insertion Que faire si l'on ne connait pas a priori la taille du résultat ? bool is_gt_0(double x) { ! return x > 0.0; } deque<double> dx = { … }; vector<double> vec; vec est initialement le vecteur vide auto it = copy_if(dx.begin(), dx.end(), back_inserter(vec), &is_gt_0); Un itérateur d'insertion crée un nouvel élément chaque fois qu'il progresse (++) Si les algorithmes ne peuvent modifier la taille d'une collection, les itérateurs le peuvent ! C++ de base ©2013 — Jean-Paul Rigault 73 Algorithmes de la STL Exemples d'algorithmes (5) Copie de collections de même type de valeur : itérateurs d'insertion auto it = copy_if(dx.begin(), dx.end(), front_inserter(vec), &is_gt_0); map<string, string> m0 = { … }; map<string, string> m1; copy(m0.begin(), m0.end(), inserter(m1, m1.begin())); Création d'itérateurs d'insertion back_inserter(c) crée les nouveaux éléments avec push_back() front_inserter(c) crée les nouveaux éléments avec push_front() inserter(c, pos) crée les nouveaux éléments avec insert(). L'insertion à lieu à partir de la position définie par l'itérateur pos C++ de base Algorithmes de la STL Exemples d'algorithmes (6) Copie de collections de types de valeurs possiblement différents string toupper(const string& s) { ! string sres; ! transform(s.begin(), s.end(), back_inserter(s.res), ::toupper); ! return sres; } La fonction toupper(char) set<string> sstr = { … }; transform(sstr.begin(), sstr.end(), sstr.begin(), &toupper); de C (dans <cctype>) C++ de base 75 ©2013 — Jean-Paul Rigault ©2013 — Jean-Paul Rigault Algorithmes de la STL Exemples d'algorithmes (7) Copie de collections de types de valeurs possiblement différents double half(int a) {return double(a)/2;} set<int> si = {1, 1, 2, 3, 5, 8, 13}; list<double> halves(si.size()); transform(si.begin(), si.end(), halves.begin(), &half); Le mécanisme des templates de C++ impose de respecter scrupuleusement la conformance de type indiquée • • Noter la transformation sur place 74 La fonction de transformation prend un paramètre de type int car elle sera appliquée à chaque élément d'une collection d'int Elle retourne un double, car la collection résultat est une collection de double Si cette conformance n'est pas respectée, préparez-vous à des messages d'erreur terrifiants ! C++ de base 76 ©2013 — Jean-Paul Rigault Algorithmes de la STL Algorithmes et lambda-expressions (1) Dans les exemples précédents nous avons définis des fonctions auxiliaires ad hoc comme is_gt_0, toupper(), half(), etc. Ce n'est pas toujours commode car C++ demande que ces fonctions soient définie globalement (pas de fonctions locales) donc la définition de la fonction est loin de l'endroit où elle est utilisée (l'algorithme) la fonction ne peut pas profiter de données locales En outre, les algorithmes ont des problèmes avec les opérateurs list<int> li = {1, 2, 3, 4}; transform(li.begin(), li.end(), li.begin(), operator-); Ceci ne compile pas L'opérateur est surchargé et le contexte fournit trop peu d'information pour le résoudre • • La STL fournit plusieurs solutions, la plus simple étant les λ-expressions C++ de base 77 ©2013 — Jean-Paul Rigault Algorithmes de la STL Algorithmes et lambda-expressions (3) Algorithmes de la STL Algorithmes et lambda-expressions (2) λ-expressions λ-calcul d'Alonzo Church (1936), un système formel de définition des fonctions calculables • équivalent à la machine de Turing (1936) et d'ailleurs à tous les autres modèles de calculabilité Le λ-calcul sert de base au langage Lisp de John McCarthy (1958), le premier des langages fonctionnels Principe de la notation : transformer une expression en fonction en spécifiant les variables « libres » x+y+2!! ! ! λx.x+y+2!! ! λxλy.x+y+2! ! une expression une fonction à une variable une fonction à deux variables x ⟼ x+y+2 x,y ⟼ x+y+2 Évidemment C++ n'utilise pas cette notation géniale... ©2013 — Jean-Paul Rigault 78 C++ de base Algorithmes de la STL Algorithmes et lambda-expressions (4) Exemples de λ-expressions en C++ transform(li.begin(), li.end(), li.begin(), [] (int a) {return -a;}); Anatomie d'une λ-expression [...] (P1 p1, ..., Pn pn) -> T { . . . } transform(si.begin(), si.end(), halves.begin(), [] (int a) {return double(a)/2;}); auto it = copy_if(dx.begin(), dx.end(), back_inserter(vec), [] (double x) {return x > 0.0;}); Début de la λ Aussi liste de capture set<string> sstr = { … }; int n = count_if(sstr.begin(), sstr.end(), [] (const string& s) { return s.size() > 5; }); C++ de base 79 Type de retour (le plus souvent déduit par le compilateur Liste de paramètres de fonction Bloc de définition de fonction Définit une fonction T anonyme(P1 p1, ..., Pn pn) Les parties de syntaxe en Magenta sont optionnelles ©2013 — Jean-Paul Rigault C++ de base 80 ©2013 — Jean-Paul Rigault Algorithmes de la STL Algorithmes et lambda-expressions (5) Algorithmes de la STL Algorithmes et lambda-expressions (6) Anatomie d'une λ-expression (suite) Liste de capture Anatomie d'une λ-expression (suite) Aucune limitation sur la liste de paramètres, le type de retour ou le contenu du bloc de fonction • Permet de rendre accessibles au corps de la λ certaines données locales, mais sans passer ces données en paramètre []! ! ! rien n'est capturé [=]!! ! toutes les données locales sont capturées, par valeur (copie) [&]!! ! toutes les données locales sont capturées, par référence [x, y]!! capture seulement x, y par valeur [&x, &y]! capture seulement x, y par référence [&x, y]! capture x par référence et y par valeur etc. Cependant, la syntaxe des λ-expressions suggère que la fonction doit être relativement courte Type de retour • Déduit automatiquement ppar le compilateur si aucune ambiguïté • Tous les return du bloc doivent renvoyer le même type C++ de base ©2013 — Jean-Paul Rigault 81 Algorithmes de la STL Algorithmes et lambda-expressions (7) list<string> filter_long_string(const list<string>& lstr, size_t n) { ! list<string> result; ! copy_if(lstr.begin(), lstr.end(), back_inserter(result), [ ] (const string& s) {return s.size() > n;}); ! return result; } d’une variable Utilisation d”une variable locale dans le corps de la λ 83 ©2013 — Jean-Paul Rigault Algorithmes de la STL Algorithmes et lambda-expressions (8) Nécessité de capturer les variables locales Nécessité de capturer les variables locales C++ de base 82 Exemples de listes de capture Exemples de listes de capture Ne compile pas ! C++ de base ©2013 — Jean-Paul Rigault list<string> filter_long_string(const list<string>& lstr, size_t n) { ! list<string> result; ! copy_if(lstr.begin(), lstr.end(), back_inserter(result), [n] (const string& s) {return s.size() > n;}); ! return result; } Ici capture de n par valeur C++ de base 84 ©2013 — Jean-Paul Rigault Algorithmes de la STL Algorithmes et lambda-expressions (9) Algorithmes de la STL Algorithmes et lambda-expressions (10) Exemples de listes de capture Capture par référence • Permet de modifier une variable locale d'un bloc englobant dans le corps de la λ list<string> filter_long_string(const list<string>& lstr, size_t n) { !! list<string> result; !! size_t ncopied = 0; !! copy_if(lstr.begin(), lstr.end(), back_inserter(result), [n, &ncopied] (const string& s) { ! ! ++ncopied; ! ! return s.size() > n; }); !! if (ncopied == 0) !! ! throw No_Long_String(); !! return result; } 85 C++ de base ©2013 — Jean-Paul Rigault Exemples de listes de capture Qu'en est-il des données globales ? • Elles n'on évidemment pas besoin d'être capturées list<string> lstr = { … }; for_each(lstr.begin(), lstr.end(), [] (const string& s) {cout << s << '\n';}); count() count_if! Compte le nombre d’éléments qui ont une valeur donnée (resp. qui satisfont un prédicat) min_element() max_element() Retour le plus petit (resp. le plus grand) élément d’une collection equal() Teste l’égalité de deux collections mismatch() Retourne le premier élément différent entre deux séquences lexicographical_compare() Compare deux collections lexicographiquement find() find_if() Recherche le premier élément qui a une valeur donnée (resp. qui satisfait un prédicat) search_n() Recherche n éléments consécutifs satisfaisant une condition search() find_end() Recherche la premières (resp. la dernière) occurrence d’une sous-collection find_first_of() Recherche le premier élément parmi plusieurs adjacent_find() Recherche deux éléments consécutifs égaux C++ de base 87 ©2013 — Jean-Paul Rigault 86 C++ de base Algorithmes de la STL Panorama des algorithmes (1) Algorithmes ne modifiant pas les collections Applique une opération à chaque élément d’une collection for_each() Variable globale (en fait dans la portée du namespace std) Pas de capture ©2013 — Jean-Paul Rigault Algorithmes de la STL Panorama des algorithmes (2) Algorithmes de modifications simples for_each() copy() copy_backward() Applique une opération à chaque élément d’une collection transform() merge() Transforme une collection en une autre ou combine les éléments de deux collections pour en former une troisième Fusionne deux collections préalablement triées swap_range() fill() fill_n() Échange les éléments de deux collections remplace tous les (resp. n) éléments d’une collection avec une valeur donnée generate() generate_n() remplace tous les (resp. n) éléments d’une collection avec le résultat d’une fonction replace() replace_if() Remplace tous les éléments de valeur donnée (resp. satisfaisant une conditon) avec une valeur donnée replace_copy() replace_copy_if() Comme la ligne précédente mais le résultat est dans une autre collection C++ de base Copy une collection à partir du début (resp. de la fin) 88 ©2013 — Jean-Paul Rigault Algorithmes de la STL Panorama des algorithmes (3) Algorithmes retirant des éléments Enlève les éléments d’une collection de valeur donnée (resp. satisfaisant un prédicat). En fait, réorganise la collection de telle sorte que les éléments à conserver soient placés en tête et retourne un itérateur sur la fin de cette sous collection à cosnerver. remove() remove_if() remove_copy() remove_copy_if() unique() unique_copy() Copie tous les éléments qui ne sont pas égaux à une valeur donnée (resp. qui ne satisfont pas un prédicat) dans une autre collection. Enlève les éléments dupliqués consécutifs (fonctionnement identique à remove()) Remarque sur les algorithmes remove() et remove_if() Ces algorithmes n'enlèvent aucun élément Ils se contentent de réorganiser la collection list<int> li(…); auto iter = remove(l.begin(), l.end(), 5); D'où l'idiome suivant li.erase(remove(li.begin(), li.end(), 5), li.end()); li.end() iter li.begin() Copie les éléments dupliqués consécutifs dans une autre collection. Les containers associatifs n'ont ni remove(), ni remove_if(), ni unique() 89 C++ de base Algorithmes de la STL Panorama des algorithmes (4) ©2013 — Jean-Paul Rigault 90 C++ de base Algorithmes de la STL Panorama des algorithmes (5) Algorithmes de permutation, rotation, brassage Renverse un collection sur place (resp. en copiant le reverse() résultat dans une autre collection) reverse_copy() On peut de débarasser de ces éléments Tous les éléments de li non égaux à 5 ©2013 — Jean-Paul Rigault Algorithmes de la STL Panorama des algorithmes (6) Algorithmes de tri sort() stable_sort() Trie une collection sur place (en préservant l’ordre des éléments égaux) rotate() rotate_copy() Effectue une permutation circulaire sur place (resp. en copiant le résultat dans une autre collection) partial_sort() partial_sort_copy() Trie les N premiers éléments (et les copie dans une autre collection) next_permutation() prev_permutation() Génère des permutations nth_element() random_shuffle() Brasse ou bat les éléments (comme on « bat les cartes »), les mettant ainsi dans un ordre aléatoire Découpe une collection en deux sous cellection par rapport à une position donnée, de telle sorte que cahque élément de la première collection est inférieur à chque élément de la seconde. partition() stable_partition() Change l’ordre des éléments de telles sortes que ceux satisfaisant une condition soient en tête (en préservant l’ordre telatif de la collection originale) et retourne un itérateur sur la fin de la collection de tête make_heap() push_heap() pop_heap() sort_heap() Manipulation de « tas » (« heap ») une représentation particulière des arbres binaires partition() stable_partition() Change l’ordre des éléments de telles sortes que ceux satisfaisant une condition soient en tête (en préservant l’ordre telatif de la collection originale) et retourne un itérateur sur la fin de la collection de tête Les containers associatifs n'ont pas ces opérations C++ de base 91 ©2013 — Jean-Paul Rigault C++ de base 92 ©2013 — Jean-Paul Rigault Algorithmes de la STL Panorama des algorithmes (7) Remarque sur les algorithmes de tri These algorithms sort the elements according to a sort criterion • • operator< by default or a binary predicate given as a (last) parameter of the algorithm L'ordre doit être un strict weak ordering Une relation d’ordre partielle strict dans laquelle l’incomparabilité est une relation d’équivalence • • • • pour tout x, x ≺ x est faux (non réflexif) pour tout x et y, si x ≺ y alors y ≺ x est faux (non symétrique) pour tout x, y et z, si x ≺ y et y ≺ z, alors x ≺ z (transitif) pour tout x, y et z, si x et y sont incomparables et si y et z sont incomparables, alors x et z sont incomparables (l’incomparabilité est transitive) Attention: sort() et stable_sort() ne sont pas utilisables avec list Ces algorithmes requièrent un random access iterator On peut utiliser la fonction membre list::sort() à la place 93 C++ de base ©2013 — Jean-Paul Rigault Algorithmes de la STL Panorama des algorithmes (8) Algorithmes sur collections triées binary_search() includes() Vérifie l’appartenance d’un élément à une collection Vérifie si une collection est incluse dans une autre lower_bound() upper_bound() Recherche le premier élément qui ne soit pas inférieur à (resp. strictement plus grand qu’) une valeur donnée equal_range() Retourne le premier interval d’itération d”éléments consécutifs égaux à une valeur donnée merge() Fusionne deux collections set_union() set_intersection() Union et intersection ensemblistes de deux collections (le résultat allant dans une troisième) Copie tous les éléments d’une collection qui n’appartiennent pas à une seconde collection dans une troisième set_symetric_difference() Étant données deux collections, copie tous les éléments qui n’appartiennent qu’à une seule de ces deux dans une troisième Fusionne deux sous-collections consécutives inplace_merge() set_difference() 94 C++ de base Algorithmes de la STL Panorama des algorithmes (9) ©2013 — Jean-Paul Rigault Algorithmes de la STL Algorithmes ou boucles explicites ? Algorithmes numériques iota() Remplit une collection avec des éléments de valeurs consécutives (selon l’opérateur ++) accumulate() Calcule la somme (ou autre opération) des éléments d’une collection Calcule le produit interne (i.e. la somme des produits ou autres opérations) de deux collections inner_product() adjacent_diference Calcule les différences entre 2 éléments consécutifs d’une collection et les copie dans une autre collection Calcule les sommes partielles des éléments d’une collection et partial_sum() les copie dans une autre collection Les algorithmes de la STL sont effectivement des boucles sur les éléments des collections Utiliser des algorithmes (au lieu de boucles explicites) a de nombreux avantages Code plus lisible • Intention claire • Code compact Complexité garantie Uniformité de l’interface Possibilité d’optimisation • Dépendant de la nature des containers utilisés C++ de base 95 ©2013 — Jean-Paul Rigault C++ de base 96 ©2013 — Jean-Paul Rigault La classe C++ comme type abstrait Cours C++ de base Introduction à la classe C++ Une classe (arithmétique) simple : Rational Membres de classes Constructeurs, opérations de copie par défaut, conversions implicites Définition d’opérateur Utilisation d’une classe comme membre d’une autre classe : Gauss_Rational partie 3 Une classe plus compliquée : Document La classe C++ comme type abstrait Construction, destruction Définition des opérations de copie Pointeurs partagés (shared_ptr) Opérateur d’indexation 97 C++ de base ©2013 — Jean-Paul Rigault La classe C++ Une classe simple : Rational Extension de la structure C Représenter et manipuler des nombres rationnels Champs (membres) fonctionnels classes d’équivalence de fractions p/q (où p, q ∈ Z) Contrôle d’accès opérations arithmétiques (corps commutatif) Opérations d’initialisation, de destruction et de copie définissables Définition de la classe Rational (Héritage) Définition d’un nouveau type Représentation interne : la structure C sous-jacente Opérations licites sur les objets (instances) Le nouveau type est utilisable dans tous les contextes de C++ manipulant des types (orthogonalité) Aussi peu de différence que possible entre les types de base (int, double...) et les types définis pas des classes (string, vector<int>...) C++ de base 99 ©2013 — Jean-Paul Rigault 98 C++ de base ©2013 — Jean-Paul Rigault ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! class Rational { private: ! int _numerator;! ! ! int _denominator; public: ! int numerator() const; ! int denominator() const; ! // ... }; C++ de base Membres de donnée privés accessibles uniquement dans le corps de la classe Fonctions-membres publiques accessibles partout 100 ©2013 — Jean-Paul Rigault Une classe simple : Rational Construction (1) Initialisation (construction) d’un objet de type Rational class Rational { private: int _numerator; int _denominator public: Rational(); Rational(int, int = 1); // ... }; Definition d’un constructeur pour la classe Rational Verify the construction parameters Normalize the representation Initialize the object Constructeur par défaut (sans argument) Constructeurs : fonctions-membres de même nom que la classe, responsables de son initialisation 101 C++ de base Une classe simple : Rational (3) Construction (2) ©2013 — Jean-Paul Rigault Rational::Rational(int p, int q) { if (q == 0) throw Null_Denominator(); int g = gcd(p, q); _numerator = sign2(p, q)*abs(p)/g; _denominator = abs(q)/g; } C++ de base Une classe simple : Rational (3) Construction (3) 102 ©2013 — Jean-Paul Rigault Une classe simple : Rational (3) Construction (4) Definition d’une exception pour la classe Rational class Rational { // ... public: class Null_Denominator {}; // ... }; Définition du constructeur par défault class Rational { int _numerator; int _denominator; public: Rational() : _numerator(0), _denominator(0) {} Rational(int p, int q = 1); // ... }; Récupération de l’exception try { Rational r(p, q); // suite normal du calcul avec r } catch (Rational::Null_Denominator){ cerr << “bad construction\n” } C++ de base 103 ©2013 — Jean-Paul Rigault C++ de base 104 ©2013 — Jean-Paul Rigault Une classe simple : Rational (3) Construction (4) Définition du constructeur par défault (C++11) Définition du constructeur par défault (C++11) class Rational { Initialisation directe des membres de donnée d’instance int _numerator = 0; int _denominator = 1; Constructeur par défaut obligatoire public: à cause de la présence de l’autre Rational() {} constructeur Rational(int p, int q = 1); // ... }; 105 C++ de base Une classe simple : Rational (3) Construction (4) ©2013 — Jean-Paul Rigault class Rational { Initialisation directe des membres de donnée d’instance int _numerator = 0; int _denominator = 1; public: Force la génération automatique Rational() = default; du constructeur par défault Rational(int p, int q = 1); // ... }; Une classe simple : Rational (3) Construction (5) C++ génère automatiquement l’appel d’un des constructeurs de la classe chaque fois qu’une instance est créée Ce constructeur est invoqué après que l’ensemble de la structure C sousjacente de l’instance ait été allouée Quand un constructeur admet des arguments, ceux-ci doivent être fournis lors de la création Rational r(5, 9); Un constructeur sans argument est dit constructeur par défaut Rational r0; // construit le rationnel 0/1 (0) Attention au piège, n’écrivez pas Rational r0(); Ceci définit une fonction void ➝ Rational, pas une instance de Rational • • 107 ©2013 — Jean-Paul Rigault Une classe simple : Rational Copie La classe Rational est entièrement définie par son contenu Elle ne fait aucune référence à d’autres instances (d’autres classes) En conséquence les opérations de copie se font par défaut Les constructeurs peuvent être surchargés C++ de base 106 C++ de base ©2013 — Jean-Paul Rigault Rational r(3,2); Copie à l’initialisation Rational r1 = r; r = r1; Copie à l’affectation La copie se fait membre à membre Chaque membre est copié selon ses propres modalités de copie • Ici, les membres étant des entiers (int) ils sont copiés par valeur C++ de base 108 ©2013 — Jean-Paul Rigault Une classe simple : Rational Fonctions-membres (2) Définition d’une fonction-membre en dehors de la classe Utilisation des fonctions-membres Rational r(3, 2); Argument implicite int n = r.denominator(); Définition d’une fonction-membre en dehors de la classe Cette définition est dans un fichier-source .cpp int Rational::denominator() const { return _denominator; Le const final signifie que } l’argument implicite (r ci-dessus) n’at pas modifié par l’appel de cette fonction Il s’agit du membre _denominator de l’argument 109 C++ de base Une classe simple : Rational Fonctions-membres (2) ©2013 — Jean-Paul Rigault Cette définition est alors dans le fichier-source .h class Rational { // ... public: // ... int denominator() const { return _denominator; } Une fonction déclarée ainsi (son // ... corps étant défini dans la classe) est implicitement inline }; 110 C++ de base ©2013 — Jean-Paul Rigault Une classe simple : Rational Conversion (1) Une classe simple : Rational Conversion (2) Un constructeur invocable avec un seul argument définit une conversion implicite du type de l’argument vers celui de la classe Il est possible de définir une conversion implicite dans l’autre direction Une conversion implicite peut être utilisée directement par le compilateur class Rational { // ... public: Rational(int p, int q = 1); // ... }; Rational Rational Rational Rational C++ de base r r r r = = = = Conversion int ➝ Rational Conversion automatique Toutes ces formes sont 3; équivalentes et définissent r Rational(3); comme égal à 3/1 (Rational)3; static_cast<Rational>(3); 111 ©2013 — Jean-Paul Rigault class Rational { Conversion Rational ➝ double // ... public: operator double() const { return double(_numerator) / double(_denominator); }; Conversion automatique // ... x égale 0.8 }; Rational r(4, 5); double x = r; x = r * 2 + r/2; C++ de base Mélange de type : l’expression est évaluée en double (résultat : 2.0) 112 ©2013 — Jean-Paul Rigault Une classe simple : Rational Surcharge des opérateurs (1) Une classe simple : Rational Surcharge des opérateurs (2) Opérateur-membre “multiplication” Définition Opérateur-membre “moins unaire” Définition Rational Rational::operator-() const { return Rational(-_numerator, _denominator); } Utilisation Équivalent à r.operator-() Rational r(3, 2); Rational r1 = -r; r = -r1; Équivalent à r1.operator-() 113 C++ de base ©2013 — Jean-Paul Rigault Rational Rational::operator*(Rational r) const { return Rational(_numerator * r._numerator, _denominator * r._denominator); } Utilisation Rational r1(3, 2); Rational r2(5, 9); Rational r; r = r1 * r2; r = r1 * 3; r = 3 * r1; 1. Cette fonction n’est pas un membre de cette classe class Rational { 2. Elle en a cependant accès aux membres privés // ... friend Rational operator*(Rational r1, Rational r2); // ... }; Noter absence de Rational:: Rational operator*(Rational r1, Rational r2) { return Rational(r1._numerator * r2._numerator, r1._denominator * r2._denominator); } Équivalent à operator*(r1, r2) Utilisation C++ de base Équivalent à operator*(r1, Rational(3)) Équivalent à operator*(Rational(3), r1) 115 Ne compile pas ! Pas de conversion sur l’argument implicite 114 ©2013 — Jean-Paul Rigault Une classe simple : Rational Surcharge des opérateurs (4) Opérateur ami (friend) Définition r = r1 * r2; r = r1 * 3; r = 3 * r1; Équivalent à r1.operator*(Rational(3)) Résultat: 9/2 C++ de base Une classe simple : Rational Surcharge des opérateurs (3) Opérateur ami (friend) “multiplication” Équivalent à r1.operator*(r2) Résultat: 15/18 = 5/6 ©2013 — Jean-Paul Rigault Restaure la symétrie entre les opérandes • Plus d’opérande implicite • Pas de conversion implicite sur l’argument implicite • Mais certains opérateurs doivent être membre Permet les conversions implicites sur les arguments (normaux) Pour la plupart des opérateurs, le programmeur a le choix (exclusif) entre opérateur-membre et opérateur ami Une fonction peut-être amie de plusieurs classes Une classe peut avoir comme amie une fonction libre quelconque (pas seulement un opérateur) une fonction-membre d’une autre classe une classe tout entière • • C++ de base toutes les fonctions-membres de la seconde classe sont alors amies de la première mais pas les fonctions amies de la seconde : l’amitié n’est pas transitive 116 ©2013 — Jean-Paul Rigault Une classe simple : Rational Entrée/Sortie (1) La classe std::ostream définit l’opérateur << pour tous les types de base ainsi que certaines classes de la bibliothèque standard Il est possible de définir cet opérateur pour une nouvelle classe class Rational { friend ostream& operator<<(ostream& os, Rational r); // ... Les iostream se manipulent toujours par référence }; ostream& operator<<(ostream& os, Rational r) { os << r._numerator << ‘/’ << r._denominator; return os; } Toujours retourner la iostream cout << ″r = ″ << r << endl; cout cout Une classe simple : Rational Définition complète (1) class Rational { private: int _numerator = 0; int _denominator = 1;! public: // Exceptions class Null_Denominator {}; class Bad_Format {}; // Construction et conversion Rational() = default; Rational(int, int = 1); operator double() const; // Fonctions d'accès int num() const; int denom() const; // Opérateurs arithmétiques Rational operator+() const; // unary plus Rational operator-() const; // unary minus! cout C++ de base 117 ©2013 — Jean-Paul Rigault Une classe simple : Rational Définition complète (2) // Opérateurs arithmétiques (suite) friend Rational operator+(Rational, Rational); friend Rational operator-(Rational, Rational); friend Rational operator*(Rational, Rational); friend Rational operator/(Rational, Rational);!! // Opérateurs relationnels friend bool operator==(Rational, Rational); friend bool operator!=(Rational, Rational); friend bool operator<(Rational, Rational); friend bool operator<=(Rational, Rational); friend bool operator>(Rational, Rational); friend bool operator>=(Rational, Rational); // Opérateurs d'entrée-sortie friend ostream& operator<<(ostream&, Rational); friend istream& operator>>(istream&, Rational&); 118 C++ de base Une classe plus compliquée : Document Principe On considère un document, composé de paragraphes (de différents types) Document title authors compose() ... * _paragraphs Salammbô I. Le festin Paragraph contents compose() ... 119 ©2013 — Jean-Paul Rigault paragraphes C++ de base C'était à Mégara, faubourg de Carthage, dans les jardins d'Hamilcar. Les soldats qu'il avait commandés en Sicile se donnaient un grand festin pour célébrer le jour anniversaire de la bataille d'Eryx, et comme le maître était absent et qu'ils se trouvaient nombreux, ils mangeaient et ils buvaient en pleine liberté. Les capitaines, portant des cothurnes de bronze, s'étaient placés dans le chemin du milieu, sous un voile de pourpre à franges d'or, qui s'étendait depuis le mur des écuries jusqu'à la première terrasse du palais ; ... }; C++ de base ©2013 — Jean-Paul Rigault 120 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Définition partielle #include <string> #include <vector> using namespace std; class Paragraph; // forward class Document { private: ! string _title; ! vector<string> _authors; ! vector<Paragraph *> _paragraphs; ! bool _is_composed; public: ! Document(); ! Document(const string& t, ! const vector<string>& a ! ! = vector<string>()); ! void compose(int, int = 0); ! void append(const Paragraph&); ! // ... }; C++ de base class Paragraph { private: ! string _contents; public: ! Paragraph(const string& = ""); ! void compose(int, int = 0); ! // ... }; Justification des pointeurs au chapitre suivant 121 Utilisé Utilisé seulement seulement au au chapitre chapitre suivant suivant ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Constructeurs (1) Paragraph::Paragraph(const string& c) : _contents(c) { } Document::Document(const string& t, const vector<string>& a) : _title(t), _authors(a), _is_composed(false) { } Document::Document() : _title(), _authors(), _paragraphs(), _is_composed(false) { } Document::Document()! : _is_composed(false) { } C++ de base 123 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Constructeurs (1) Paragraph::Paragraph(const string& c) : _contents(c) { } Document::Document(const string& t, const vector<string>& a) : _title(t), _authors(a), _is_composed(false) { } Document::Document() : _title(), _authors(), _paragraphs(), _is_composed(false) { } Document::Document()! : _is_composed(false) { } C++ de base 122 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Constructeurs (2) Paragraph p0; Paragraph p1("Something to read"); Document d0; Document d1("ISO/IEC 14882"); void f() { vector<string> a = { "Stanley Lippman", "Josée LaJoie", ! "Barbara Moo", }; Document d("C++ Primer", a); Paragraph pref("Bla bla..."); d.append(preface); } C++ de base void Document::append( const Paragraph& p) { _paragraphs.push_back( new Paragraph(p)); } Attention : Cette version de Document::append() est provisoire (et incorrecte) 124 ©2013 — Jean-Paul Rigault Opérateurs d'allocation dynamique de mémoire : new et delete Une classe plus compliquée : Document Destructeur Remplacement (typé) des fonctions malloc() et free() de C Allocation mémoire int *pi = int *pj = int *ptab Paragraph Paragraph new int; new int(42); = new int[10]; *pp0 = new Paragraph(); *pp1 = new Paragraph("hello"); Initialisation Retour de la mémoire delete delete delete delete delete class Document { // ... public: Document(...); Document(); ~Document(); // ... }; Pas d'initialisation pi; pj; [] ptab; pp0; pp1; [] obligatoire pour un pointeur sur tableau ordinaire L'opérande de delete doit être le résultat d'un new précédent Les paragraphes étant alloués dynamiquement, il faut pouvoir les détruire lorsque l'objet Document est luimême détruit C'est le role du destructeur de la classe Document Le destructeur exécute les « dernières volontés » de l'objet Document::~Document() { for (auto p : _paragraphs) { delete p; } } delete nullptr (ou delete 0) est sans conséquence L'allocation dynamique "nue" est bien plus rare en C++ qu'en Java 125 C++ de base ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Invocation des constructeurs C++ de base 126 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Invocation du destructeur Constructeur = initialiseur Destructeur = finaliseur Invoqué juste après l'allocation mémoire de l'objet Invoqué automatiquement Invoqué juste avant la dé-allocation mémoire de l'objet • début du programme pour les objets à allocation statique • définition dans un bloc pour les objets automatiques • appel de l'opérateur new • création d'une exception avec throw Invoqué automatiquement Peut-être invoqué explicitement pour créer un objet temporaire ou forcer une conversion • fin du programme pour les objets à allocation statique (exit()) • fin du bloc pour les objets automatiques • appel de l'opérateur delete • dernière capture (catch) d'une exception N'est (presque) jamais invoqué explicitement pp->append(Paragraph("hello, world!"); C++ de base 127 ©2013 — Jean-Paul Rigault C++ de base 128 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (1) Une classe plus compliquée : Document Copie des objets (1) Copie à l'initialisation Copie à l'initialisation Document d1("The Bible"); Document d2 = d1; d2 : Document Document d1("The Bible"); Document d2 = d1; d1 : Document d2 : Document d1 : Document title = title = "The Bible" title = "The Bible" title = "The Bible" authors = authors = { "??" } paragraphs[0] paragraphs[0] paragraphs[1] paragraphs[1] paragraphs[2] paragraphs[2] C++ de base authors = { "??" } authors = { "??" } : Paragraph paragraphs[0] paragraphs[0] : Paragraph paragraphs[1] paragraphs[1] paragraphs[2] paragraphs[2] : Paragraph ©2013 — Jean-Paul Rigault 129 Une classe plus compliquée : Document Copie des objets (2) C++ de base Partage de données : Paragraph : Paragraph : Paragraph ©2013 — Jean-Paul Rigault 130 Une classe plus compliquée : Document Copie des objets (3) Copie à l'initialisation : constructeur de copie Problème induit par le partage de données void f(Document d) { // ... }; Destruction de la variable locale d ; le destructeur détruit aussi tous les paragraphes de d (cad de d1) ! Document d1("The Bible"); f(d1); cout << d1; Initialisation de la variable locale d par une copie de d1 Que reste-t-il dans d1 ? C++ de base 131 ©2013 — Jean-Paul Rigault Document::Document(const Document& d) : _title(d._title), _authors(d._authors), _is_composed(d._is_composed) { for (auto pp: d._paragraphs) append(*pp); } Par défaut, C++ invoque le constructeur de copie chaque fois qu'un objet est initialisé à partir d'un objet de même type • Initialisation simple !Document d1(…); !Document d2 = d1; Appel et retour de fonction par valeur ! Document f(Document d); Copie par défaut (voir plus loin) • Équivalent à Document d2(d1); • C++ de base 132 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (4) Une classe plus compliquée : Document Copie des objets (4) Copie à l'affectation L'affectation par défaut de Document pose le même problème de partage des paragraphes Affectation de copie Affectation de copie (suite) Document& Document::operator=(const Document& d) { if (this == &d) return *this; d = d; ??? for (int i = 0; i < _paragraphs.size(); i++) delete _paragraphs[i]; Retour à l'état Document vide _paragraphs.clear(); for (auto pp : _paragraphs) append(*pp); Copie des membres _title = d._title; _authors = d._authors; _is_composed = d._is_composed; return *this; } C++ de base 133 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (5) Ne pas confondre initialisation et affectation ! Opérations associées différentes Une seule initialisation dans la vie d'un objet, mais plusieurs affectations possibles Certains objets ne sont pas affectables mais doivent être initialisables (les constantes, const) Le code de copie est différent Cependant les deux opérations de copie doivent avoir la même notion de la valeur à copier 135 • Affectation simple Document d1(), d2(…); d1 = d2; Copie par défaut (voir plus loin) • C++ de base 134 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (6) Opérations de copie par défaut Contextes différents C++ de base Par défaut, C++ invoque le constructeur de copie chaque fois qu'un objet est initialisé à partir d'un objet de même type ©2013 — Jean-Paul Rigault Si les opérations de copie (constructeur de copie ou affectation de copie) ne sont pas explicitement définies pour une classe et que le programme tente de copier des instances de cette classe, alors C++ génère automatiquement l'opération de copie nécessaire selon le contexte L'opération de copie générée copie l'un après l'autre tous les membres de données, chacun suivant ses propres modalités de copie La génération des opérations de copie est récursive C++ de base 136 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (7) A::A(const A& a) { : _a(a._a), _pi(a._pi), _s(a._s) {} Opérations de copie par défaut (suite) class A { private: int _a; // type de base int *_pi; // pointeur string _s;// instance public: // Aucune opération // de copie }; A& A::operator=(const A& a) { _a = a._a; _pi = a._pi; _a = a._s; return *this } Noter que les pointeurs sont copiés par valeur (partage de l'objet pointé entre les deux copies) A a1(…); A a2 = a1; a1 = a2; C++ de base 137 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (8) Une classe plus compliquée : Document Copie des objets (7) A::A(const A& a) { : _a(a._a), _pi(a._pi), _s(a._s) {} Opérations de copie par défaut (suite) class A { private: int _a; // type de base int *_pi; // pointeur string _s;// instance public: // Aucune opération // de copie }; A& A::operator=(const A& a) { _a = a._a; _pi = a._pi; _a = a._s; return *this } A a1(…); A a2 = a1; a1 = a2; Noter que les pointeurs sont copiés par valeur (partage de l'objet pointé entre les deux copies) 138 C++ de base ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Opérations générées par défaut (1) Génération automatique : « The Big Four » (C++ 2003) Échec de la génération des opérations de copie par défaut Le membre constant empêche la génération de l'affectation de copie de A Le constructeur de copie est générable class A { private: const string _s; B& _rb; A(const A&); public: // … }; C++ de base Une référence se comporte comme un pointeur constant et donc empêche la génération de l'affectation de copie de A. Le constructeur de copie est générable Le constructeur de copie privé empêche la génération du constructeur de copie dans toute classe B contenant une instance de type A (ou dérivant de A) 139 ©2013 — Jean-Paul Rigault Constructeur par défaut • si la classe n'a aucun constructeur • si la classe n'a aucun destructeur • si la classe n'a pas l'opération de copie correspondante Destructeur par défaut Constructeur de copie et affectation de copie Génération automatique : « The Big Six » (C++ 2011) Les précédents… Constructeur et affectation de déplacement (move semantics) • hors de la portée de ce cours C++ ne génère automatiquement aucune autre fonction-membre que celles indiquées ici C++ de base 140 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Opérations générées par défaut (2) Une classe plus compliquée : Document Suppression des opérations Classe non copiable Forçage de la génération des opérations par défaut La classe A n'est pas consructible par défaut car elle a un autre constructeur class A { public: A(int i); A() = default; ~A() = default; A(const A&) = default; A& operator=(const A&) = default; On peut forcer la génération du constructeur par défaut... ... de même que celle des autres opérations qui possèdent une génération par défaut Les opérations de copie sont générées automatiquement Donc, si l'on veut rendre une classe non copiable, il ne suffit pas d'omettre la définition des opérations de copie Suppression d'opérations class A { Les objets de type A ne sont pas copiables public: A(int i); A() = default; A(const A&) = delete; A& operator=(const A&) = delete; // Autres membres… }; // Autres membres… On peut supprimer n'importe quelle opération, pas seulement celles qui peuvent être générées par défaut }; C++ de base 141 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Copie des objets (8) Ne pourrait-on pas recourir systèmatiquement aux opérations de copie par défaut plutôt que d’avoir à définir les opérations de copie ? Le problème vient le plus souvent des classes qui contiennent des pointeurs et de la gestion de la propriété des objets Propriété signifie ici en particulier « droit de détruire » Remplacer les pointeurs ordinaires par des pointeurs intelligents (smart pointers) • par exemple, les shared_ptr de la bibliothèque standard qui utilisent le comptage de référence pour gérer la propriété 142 C++ de base ©2013 — Jean-Paul Rigault std::shared_ptr<T> Inconvénients des pointeurs ordinaires void f() { !! int *pi = new int(3); !! int *pj = new int[3]; !! // . . . !! delete pi; !! if (*pi == 0) !! ! throw Exc(); !! else !! ! *pi = 12; !// . . . } pj n'est jamais détruit pointeurs sur objets individuels et sur tableaux undistinguables destruction explicite requise pour objets alloués dynamiquement … mais la valeur du pointeur est toujours utilisable après destruction ceci peut crasher et est de toutes façons incorrect d'où fuite de mémoire C++ de base 143 ©2013 — Jean-Paul Rigault Advanced C++ Libraries 144 ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> Resource Acquisition Is Initialization (RAII) Encapsuler une ressource dans une instance de classe à portée locale (bloc) Faire en sorte que le destructeur de la classe de l'objet libère la ressource class File_Wrapper { FILE *_fp; public: File_Wrapper(const string& name, const string& mode) { _fp = fopen(name, mode); } ~File_Wrapper() {fclose(_fp);} }; ~ofstream() void f() { ferme le fichier File_Wrapper("foo.txt", "r"); ofstream os(″bar.txt″); ~File_Wrapper() // ... ferme le fichier } 145 Advanced C++ Libraries ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> Smart Pointers et RAII template<typename T> class SP { ! T *_pt; public: ! SP(T *pt) : _pt(pt) {} ! // autres constructeurs… ! ~SP() {if (needed) delete _pt;} ! // autres opérations : *, ->, casts… }; L'objet doit être alloué par new class A { . . . }; void f() { !! SP<A> pa(new A()); !! // utiliser pa comme si // c'était un pointeur 146 Advanced C++ Libraries Un pointeur intelligent strictement utilisable pour RAII, not copyable Il doit être le seul possesseur de l'objet Il le détruit inconditionnellement à la fin du bloc Il le détruit inconditionnellement à la fin du bloc Il n'est pas copiable il n'est pas copiable !#include <memory> !void f(std::unique_ptr<A> ap); !void someFunction() { !! std::unique_ptr<A> ap1(new A()); // utiliser ap1 comme si // c'était un vrai pointeur !! std::unique_ptr<A> ap2(ap1); !! ap2 = ap1; !} Advanced C++ Libraries ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> unique_ptr (2) Il doit être le seul possesseur de l'objet !#include <memory> ~SP() détruit automatiquement l'objet si nécessaire } std::shared_ptr<T> unique_ptr (1) Un pointeur intelligent strictement utilisable pour RAII, not copyable La condition pour détruire l'objet peut varier selon le type de pointeur intelligent 147 L'objet pointé par ap est Ceci ne compile pas détruit à la fin du bloc ©2009-2013 — Jean-Paul Rigault !void f(std::unique_ptr<A> ap); !void someFunction() { !! std::unique_ptr<A> ap1(new A()); // utiliser ap1 comme si // c'était un vrai pointeur !! std::unique_ptr<A> ap2(ap1); !! ap2 = ap1; L'objet pointé par ap est !} détruit à la fin du bloc Advanced C++ Libraries 148 ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> shared_ptr Pointeur intelligent utilisant le comptage de référence Le compte de référence est géré par le pointeur lui-même, en dehors de l'objet pointé • Le constructeur de shared_ptr alloue lui-même le compte de référence shared_ptr est copiable : les opérations de copie incrémentent le compte Le destructeur de shared_ptr destructor décrémente le compte et détruit l'objet pointé lorsque ce compte atteint 0 1 2 p1 compte de référence #include <memory> using namespace std; Pointeur partagé vide shared_ptr<T> pt; // aucun objet pointé Pointeur sur un objet alloué dynamiquement shared_ptr<T> pt(new T(…)); // constructeur explicit Ce constructeur alloue un nouveau compte de référence pour l'objet pointé et l'initialise à 1 Le destructeur utilisera operator delete pour détruire l'objet pointé Fonction de commodité shared_ptr Objet p2 Advanced C++ Libraries std::shared_ptr<T> Construction des shared_ptr 149 shared_ptr<T> pt = make_shared<T>(a1, a2, …, an); presque équivalent à : shared_ptr<T> pt(new T(a1, a2, …, an)); mais en mieux ! (optimisation, garantie en cas d'exception…) • • ©2009-2013 — Jean-Paul Rigault 150 Advanced C++ Libraries std::shared_ptr<T> Opérations sur les shared_ptr (1) ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> Operations on shared_ptr (2) Specific shared pointer member functions (1) sp1.swap(sp2) or swap(sp1, sp2) Les shared_ptr sont copiables • Conversions implicites A shared_ptr<U> is implicitly convertible into a shared_ptr<T> provided that U* is implicitly convertible into T* • This is possible only if U derives from T (or if T is void) • Comparison operations Return the internal pointer (dangerous! avoid it!) T& rt = *sp • (operator*) Return a reference to the pointed object sp->f() Equality, unequality, relational operators (operator->) Return the internal pointer so that a member can be selected long n = s.use_count(); Display operation • operator<< for ostream Return the current reference count if (sp.unique()) . . . • Advanced C++ Libraries Exchange the two pointed objects T* p = sp.get() 151 ©2009-2013 — Jean-Paul Rigault Return whether the reference count of sp is 1 (unique owner) Advanced C++ Libraries 152 ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> Operations on shared_ptr (3) Specific shared pointer member functions (2) Conversion of shared pointers sp.reset() • • A shared_ptr<U> is implicitly convertible into a shared_ptr<T> provided that U* is implicitly convertible into T* Release ownership (decrement reference count) Equivalent to: shared_ptr().swap(*this) sp.reset(p) (p is a pointer to some object, possibly of different type) • • • Replace currently owned object by the one pointed by p p must be convertible into the type of the internal pointer of sp Equivalent to: shared_ptr(p).swap(*this) sp.reset(p, d) (p is a pointer as above, d is a deleter) • • • Replace currently owned object by the one pointed by p with deleter d p must be convertible into the type of the internal pointer of sp Equivalent to: shared_ptr(p, d).swap(*this) Return the address of the current deleter or 0 if not any Advanced C++ Libraries spt = static_pointer_cast<T>(spu) • spt is a shared_ptr<T>, spu is a shared_ptr<U> • U* must be convertible into T* using static_cast spt = dynamic_pointer_cast<T>(spu) • same as before but using dynamic_cast • if the cast fails, return an empty shared pointer for spt spt = const_pointer_cast<T>(spu) • same as before but using const_cast sp.get_deleter() • std::shared_ptr<T> Operations on shared_ptr (4) 153 ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> Circular Data Structures and Shared Pointers If there are no other references to these objects there is no way to delete them Memory Leak 154 Advanced C++ Libraries ©2009-2013 — Jean-Paul Rigault std::shared_ptr<T> Avantages et inconvénients 1 2 Avantage Object 1 • • Déallocation sure Ni fuite de mémoire, ni multiple destruction du même objet Évite de couteuses copies Facile à utiliser: remplacer A* par shared_ptr<A> • S’utilise ensuite dans (pratiquement) tout contexte comme un pointeur ordinaire (sur objet individuel) Évite l’exposition (et les inconvénients) de(s) pointeurs « nus » 1 Object 2 • dont l’intention en matière de propriété de l’objet pointé n’est pas toujours claire Cons Quelques limitations par rapport aux pointeurs ordinaires (diapo suivante) Encombrement mémoire : shared_ptr fait deux fois la taille d’un pointeur ordinaire Opération de copie de shared_ptr un peu ralentie par rapport à un pointeur ordinaire Les avantages dépassent largement les inconvénients : éviter les « pointeurs nus » Advanced C++ Libraries 155 ©2009-2013 — Jean-Paul Rigault Advanced C++ Libraries 156 ©2009-2013 — Jean-Paul Rigault Document avec des std::shared_ptr<T> Définition de la classe std::shared_ptr<T> Limitations Risque de fuite de mémoire en cas de structure circulaire de pointeurs Problème avec les objets qui retournent un shared_ptr sur eux-mêmes (shared from this) La bibliothèque standard propose des mécanismes pour pallier ces limitations Hors de la portée de ce cours C++ de base 157 ©2013 — Jean-Paul Rigault Document avec des std::shared_ptr<T> Utilisation de la classe void f() { vector<string> a = { "Stanley Lippman", "Josée LaJoie", "Barbara Moo", }; Document d("C++ Primer", a); shared_ptr<Paragraph> preface = make_shared<Paragraph>("Bla bla..."); auto intro = make_shared<Paragraph>(); d.append(preface); d.append(intro); } C++ de base 159 ©2013 — Jean-Paul Rigault class Document { private: string _title; vector<string> _authors; vector<shared_ptr<Paragraph>> _paragraphs; bool _is_composed; public: Document(); Document(const string& t, const vector<string>& a = {}) Document(const Document& d) = default; Document& Document::operator=(const Document& d) = default; ~Document() = default; Passer un shared_ptr évite une copie void compose(int, int = 0); void append(const shared_ptr<Paragraph>& pp) { _paragraphs.push_back(pp); } Cette version de append() est // ... maintenant définitive comme nous le }; verrons dans le chapitre suivant C++ de base 158 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Conversions implicites Conversions implicites de Paragraph Le constructeur induit string → Paragraph • Cette conversion est bienvenue Conversions implicites de Document Le constructeur principal induit string → Document Cette conversion n'est pas très logique ! ! ! ! string s = "hello"; Document d = s;! d est un document vide dont le titre est "hello" On peut inhiber la conversion implicit en marquant le constructeur explicit explicit Document(const string& t, const vector<string>& a = {}); C++ de base 160 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Un opérateur spécial : l'indexation (1) Une classe plus compliquée : Document Un opérateur spécial : l'indexation (2) Problème : operator[] et documents constants On veut atteindre le nième paragraphe d'un document, pour le lire ou le modifier Document d(…); d[0] = "Une préface…"; d[0] = d[0] + " un peu plus longue"; Paragraph& Document::operator[](int i) { return *_paragraphs[i]; } Cependant cette définition ne fonctionne pas bien pour les documents constants 161 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Un opérateur spécial : l'indexation (2) Il faut en fait deux opérateurs Un pour les documents non constants Paragraph& Document::operator[](int i) { return *_paragraphs[i]; } Il n'a pas de const final et retourne une référence sur une variable • Un autre pour les documents constants const Paragraph& Document::operator[](int i) const { return *_paragraphs[i]; } Il a un const final (indispensable pour les documents constants) et retourne une référence sur une constante • Le mécanisme de résolution de surcharge de C++ sait faire la distinction au moment de l'appel C++ de base 163 Avec la définition précédente, b[0] ne compile pas Ajoutons un const final à operator[] Définition de operator[] C++ de base const Document b("The Bible"); Document r("Mon rapport de stage"); ©2013 — Jean-Paul Rigault Paragraph& Document::operator[](int i) const { return *_paragraphs[i]; } b[0] compile, retourne une référence variable et donc permet de modifier le (premier paragraphe) du document ! • Faisons en sorte que operator[] retourne une référence sur une constante const Paragraph& Document::operator[](int i) const { return *_paragraphs[i]; } b[0] et r[0] compilent et ne son pas modifiables; or r[0] devrait l'être ! • C++ de base 162 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Définition complète (1) class Document { private: string _title; vector<string> _authors; vector<shared_ptr<Paragraph>> _paragraphs; bool _is_composed; public: // Constructeurs et destructeur Document(); Document(const string& t, const vector<string>& a = {}) Document(const Document& d) = default; Document& Document::operator=(const Document& d) = default; ~Document() = default; // Affectations Document& operator=(const Document&) = default; Document& operator+=(const Document&); Document& operator+=(const Paragraph&);! // to be continued...! C++ de base 164 ©2013 — Jean-Paul Rigault Une classe plus compliquée : Document Définition complète (2) // Fonctions d'accès const string& title() const; const vector<string>& authors() const; // Opérations diverses void compose(int, int = 0); void append(const shared_ptr<Paragraph>& pp); // Comparaisons friend bool operator==(const Document&, const Document&); friend bool operator!=(const Document&, const Document&); // Opérateurs d'indexation class No_Such_Paragraph {}; // exception Paragraph& operator[](int); const Paragraph& operator[](int) const; // Opérateur d'affichage friend ostream& operator<<(ostream&, const Document&); Cours C++ de base partie 4 Héritage et programmation par objets }; C++ de base 165 ©2013 — Jean-Paul Rigault 166 C++ de base Héritage et programmation par objets Variantes de Paragraph Divers types de paragraphes Paragraph contents Titres, sections, énumérations, listes à puces, etc. Introduction à la dérivation: variantes de Paragraph Typage dynamique et fonctions virtuelles Partage des propriétés communes Opérations de composition Arbres d'expression arithmétiques : la classe Expr Propriétés spécifiques Numérotation, indentation… Mise en page, polices et styles de caractères... 167 compose() ... Contenu (une string) Composition de diverses sortes de Paragraphs C++ de base ©2013 — Jean-Paul Rigault ©2013 — Jean-Paul Rigault C++ de base Item_Paragraph depth compose() ... 168 Section_Paragraph depth sect_counters compose() ... ©2013 — Jean-Paul Rigault Variantes de Paragraph Définition des classes dérivées (1) class Item_Paragraph : public Paragraph { private: Profondeur int _depth; (niveau d'indentation) public: Item_Paragraph(int d = 0); Item_Paragraph(const string& c, int d = 0); int depth() const {return _depth;} void compose(int, int = 0); // ... }; C++ de base 169 ©2013 — Jean-Paul Rigault Variantes de Paragraph Définition des classes dérivées (2) class Section_Paragraph : public Paragraph { private: Numéros de section, sous-section, sous-sous section, etc. int _depth; vector<int> _sect_counters; public: Section_Paragraph(const vector<int>& scnt, const string& c = "", int d = 0); int depth() const {return _depth;} string sect_number() const; void compose(int, int = 0); // ... }; 170 C++ de base Variantes de Paragraph Définition des classes dérivées (3) ©2013 — Jean-Paul Rigault Variantes de Paragraph Définition des classes dérivées (3) Une classe dérivée peut ajouter des propriétés en plus de celles de sa classe de base membres de données Un Item_Paragraph est un Paragraph Un Item_Paragraph hérite des propriétés d'un Paragraph Sa structure C sous-jacente contient celle d'un Paragraph • Elle peut contenir en plus des membres de données spécifiques On peut appliquer à un Section_Paragraph toutes les opérations déclarées pour Paragraph On peut substituer une instance de Section_Paragraph à toute instance de Paragraph (principe de substituabilité) fonctions-membres fonctions amies Une classe dérivée peut redéfinir certaines des fonctions-membres héritée Le prototype de la fonction-membre redéfinie doit être le même que dans la classe de base La profondeur de dérivation peut être quelconque C++ supporte l'héritage simple et multiple Héritage simple : une seule classe de base Héritage multiple : plusieurs classes de base distinctes C++ de base 171 ©2013 — Jean-Paul Rigault C++ de base 172 ©2013 — Jean-Paul Rigault Variantes de Paragraph Construction et copie des classes dérivées (1) Constructeurs de Item_Paragraph et Section_Paragraph Item_Paragraph::Item_Paragraph(const string& c = "", int d = 0) ! : Paragraph(c), _depth(d) {} Section_Paragraph::Section_Paragraph( const vector<int>& scnt, const string& c = "", int d = 0) ! : Paragraph(c), _depth(d), _sect_counters(scnt) {} 173 C++ de base ©2013 — Jean-Paul Rigault Variantes de Paragraph Construction et copie des classes dérivées (2) Variantes de Paragraph Construction et copie des classes dérivées (2) Ordre de construction d'une classe dérivée 1. Construction (initialisation) de la (des) classe(s) de base 2. Construction (initialisation) des membres propres de la classe dérivée Les membres de données non-héritées 3. Exécution du corps du constructeur Destruction order (inverse order) 1. Exécution du corps du destructeur 2. Destruction (finalisation) des membres propres de la classe dérivée 3. Destruction (finalisation) de la (des) classe(s) de base ADVANCED C++ — Bangalore ©2013 — Jean-Paul RIGAULT 174 Variantes de Paragraph Construction et copie des classes dérivées (4) class B {public: B(int = 0);}; Les opérations synthétisables par défaut sont générées comme pour les classes non dérivées class A {private: int _p; public: ! A(int = 0); ! // ... ➁ }; • • • • class A1 : public A { protected: B _b1; public: A1(int i = 0, int j = 1) : _b1(i), A(j) {...} ➃ ! // ... }; Opérations par défaut constructeur par défaut destructeur constructeur et affectation de copie (constructeur et affectation de transfert (move)) La seule différence est que la (les) classe(s) de base sont considérés comme des membres de données (anonymes) ADVANCED C++ — Bangalore 175 ©2013 — Jean-Paul RIGAULT A ➂ A1 _b1 B _b2 A2 class A2 : public A1 {private: B _b2;}; ➀ A1 a1(2, 3); ADVANCED C++ — Bangalore 176 ©2013 — Jean-Paul RIGAULT Variantes de Paragraph Construction et copie des classes dérivées (5) class B {public: B(int = 0);}; class A {private: int _p; public: ! A(int = 0); ! // ... ➂ }; class A1 : public A { protected: B _b1; public: A1(int i = 0, int j = 1) : _b1(i), A(j) {...} ➄ ! // ... }; ➃ ➁ A2::A2() : A1(), _b2() ➅ {} Constructeur par défaut par défaut class A2 : public A1 {private: B _b2;}; A2 a2; ➀ ADVANCED C++ — Bangalore Aucun constructeur 177 ©2013 — Jean-Paul RIGAULT Variantes de Paragraph Construction et copie des classes dérivées (6) Copie par défaut La copie automatique d'un Paragraph class Paragraph { invoque la copie de la string interne private: string _contents; public: // pas d'opérations de copie explicites }; class Section_Paragraph : public Paragraph { private: int _depth; La copie copie automatique automatique d'un d'un La vector<int> _sect_counters; Section_Paragraph invoque invoque Section_Paragraph public: la copie copie du du Paragraph, Paragraph, d'un d'un la int et et d'un d'un vector<int> vector<int> // pas d'opérations de copie int // explicites }; Variantes de Paragraph Utilisation des classes dérivées publiques On peut utiliser une instance, un pointeur, une référence sur une classe dérivée comme resp. une instance, un pointeur, une référence sur sa classe de base C++ définit trois nouvelles conversions implicites en cas d'héritage Tout instance de classe dérivée peut être convertie en une instance de sa classe de base (troncature) Constructeur de copie de Paragraph Item_Paragraph ip1; Paragraph p = ip1; Tout pointeur (resp. référence) sur une classe dérivée peut être converti en un pointeur (resp. une référence) sur la partie classe de base de l'objet pointé vector<int> counters = { … }; Section_Paragraph sp2(counters); // ...! ! cout << ip1 + sp2; C++ de base 179 Opérateurs + et << de Paragraph ©2013 — Jean-Paul Rigault 178 C++ de base ©2013 — Jean-Paul Rigault Variantes de Paragraph Accès aux membres hérités Accès à un membre privé de la classe de base void Section_Paragraph::a_function() { _contents.push_front("bla bla bla..." ); } Erreur ! Les membres privés de la classe de base sont inaccessibles dans la classe dérivée Membres protégés class Paragraph { protected: string _contents; public: // ... }; Un membre protégé est accessible dans les classes dérivées (publiques) Un membre de données dit « protégé » est tout aussi vulnérable que s'il était public • C++ de base Les fonctions protégées sont un peu plus sures et utiles 180 ©2013 — Jean-Paul Rigault Variantes de Paragraph Héritage et amitié Variantes de Paragraph Résolution de noms et héritage (1) Une classe dérivée n'hérite pas les amis de sa classe de base L'amitié n'est pas transitive en C++ Cependant, il est possible d'utiliser une fonction amie de la classe de base avec des paramètres de type d'une classe dérivée (publique) class A { public: friend f(A); // ... }; class B : public A { … }; ©2013 — Jean-Paul Rigault utilisé pour les noms nondépendents (sans ::) • à partir de l'opérateur :: de leur nom Les fonctions amies ne font pas partie de la portée de la classe C++ de base class A { ! class B { }; ! B m(B); ! friend void f(B); ! // ... }; A::B A::m(B b) { ! B b1; ! // ... } void f(A::B b) { ! A::B b2; ! // ... } 183 ©2013 — Jean-Paul Rigault La fonction est membre d'une classe Paramètre de fonction ? Hiérarchie de blocs ? 182 C++ de base Hiérarchie de classes ? La fonction est libre ADL: Argument dependent Lookup Variantes de Paragraph Résolution de noms et héritage (2) Portée d'une classe Le corps de la définition Le corps des définitions de fonctions-membres Hiérarchie de namespaces ? • blocs • classes • namespaces détails hors de la portéé de ce cours 181 C++ de base namespace global ? 3 espaces de nommage hiérarchiques Il existe un second mécanisme de résolution de noms parallèle au précédent La conversion implicite d'héritage convertit b en un A B b; f(b): Résolution de noms dite normale identificateur à résoudre: id ©2013 — Jean-Paul Rigault Variantes de Paragraph Résolution de noms et héritage (3) class A { public: int i; int j; int n; }; int i; // global variable void C::f(double n) { ! k = 0; ! // this-> k, C::k ! n = 3.14;! ! // function parameter ! j = 2; ! // B::j, but not ! // accessible here ! i = 3; // A::i ! i = ::i; ! // ::i is global i } class B : public A { private: int j; }; class C : public B { private:! ! int k; public: void f(double); }; C++ de base 184 ©2013 — Jean-Paul Rigault Variantes de Paragraph Retour sur les conversions d'héritage (1) La fonction Paragraph::compose() calcule la mise en page de chaque paragraphe Sa définition dépend du type exact du Paragraph Elle doit donc être redéfinie dans chaque sous-type de Paragraph La redéfinition doit avoir la même signature dans toute la hiérarchie C++ de base class Paragraph { // ... void compose(int, int) {...} }; class Item_Paragraph : public Paragraph { // ... void compose(int, int) {...} }; class Section_Paragraph : public Paragraph { // ... void compose(int, int) {...} }; ©2013 — Jean-Paul Rigault 185 Variantes de Paragraph Retour sur les conversions d'héritage (2) On suppose que compose() a été rédéfinie comme dans la diapo précédente La conversion implicite d'héritage convertit class Paragraph { // ... virtual compose(int, int); }; void Document::compose(int w, int m) { for (shared_ptr<Paragraph> pp : _paragraphs) pp->compose(width, ind); } Classe Expr Arbres d'expressions arithmétiques Le mot-clé virtual doit être spécifié dans la classe de base ; il est inutile (mais non nuisible) de le répéter lors de la redéfinition de la fonction dans les classes dérivées * Lors de l'appel à travers un pointeur ou une référence, le type exact de *this (à l'exécution) permettra de déterminer la version de la fonction à invoquer - / 4 5 2 Invoque Item_Paragraph::compose() void Document::compose(int w, int m) { for (shared_ptr<Paragraph> pp : _paragraphs) pp->compose(width, ind); Invoque compose() du } type dynamique de *pp C++ de base ©2013 — Jean-Paul Rigault 186 C++ de base Le typage dynamique est activé pour la fonction compose() Item_Paragraph ip; Paragraph *pp = &ip; pp->compose(80, 0); Ici, pas de troncature de l'objet *ip ! Cependant, C++ utilise par défaut le typage statique. C'est donc encore Paragraph::compose() qui est invoquée Paragraph *pp = &ip; pp->compose(80, 0); Variantes de Paragraph Retour sur les conversions d'héritage (3) Fonctions-membres virtuelles ip en un Paragraph en copiant la partie Paragraph de ip. La partie Item_Paragraph est donc perdue (troncature). Il est donc normal que Paragraph::compose() soit invoquée Item_Paragraph ip; Paragraph p = ip; p.compose(80, 0); 187 ©2013 — Jean-Paul Rigault + 2 C++ de base -(2 + 3) * 4 - 5 / 2 3 188 ©2013 — Jean-Paul Rigault Classe Expr En C(++) procédural (1) Classe Expr En C(++) procédural (2) int Eval(const Expr *e) { switch (e->arity) { case CONST: return e->val; case UNARY: switch (e->unary.un_op) { case UN_PLUS: return Eval(e->unary.op); case UN_MINUS: return -Eval(e->unary.op); } case BINARY: switch (e->binary.bin_op) { case BIN_PLUS: return Eval(e->binary.op1) + Eval(e->binary.op2); case BIN_MINUS: return Eval(e->binary.op1) - Eval(e->binary.op2); // ... } } } struct Expr { enum {CONST, UNARY, BINARY} arity; union { int val; struct { enum {UN_PLUS, UN_MINUS} un_op; Expr *op; } unary; struct { enum {BIN_PLUS, BIN_MINUS, BIN_MUL, BIN_DIV} bin_op; Expr *op1, *op2; } binary; }; // anonymous union (C++ only) }; ©2013 — Jean-Paul Rigault 189 C++ de base C++ de base Classe Expr En C++ orienté-objets (1) eval() Constant eval() Uniplus eval() Classes et méthodes abstraites Binary eval() eval() Uniminus eval() Plus eval() Minus eval() Mult eval() Div eval() Classes (et méthodes) concrètes C++ de base 191 ©2013 — Jean-Paul Rigault Classe Expr En C++ orienté-objets (2) Expr Unary 190 ©2013 — Jean-Paul Rigault class Expr { Fonction-membre virtuelle pure public: (méthode abstraite) : pas de corps virtual int eval() const = 0; dans cette classe Rend la classe abstraite (pas }; d'instance) class Unary : public Expr { protected: Expr *op; Ces Ces deux deux classes classes héritent héritent de de la la public: fonction fonction virtuelle virtuelle pure pure eval() eval() Unary(Expr *e) : op(e) {} Elles Ellessont sontdonc doncaussi aussiabstraites abstraites }; class Binary : public Expr { protected: Expr *op1, *op2; public: Binary(Expr *e1, Expr *e2) : op1(e1), op2(e2) {} }; C++ de base 192 ©2013 — Jean-Paul Rigault Classe Expr En C++ orienté-objets (3) Classe Expr Résolution des fonctions virtuelles (1) class Constant : public Expr { private: int val; public: Constant(int v) : val(v) {} Définitions Définitionsde delalafonction-membre fonction-membre int eval() const {return val;} virtuelle virtuelleeval(). eval().Ces Cesclasses classes }; deviennent deviennentdonc donccontrètes contrètes class Uniminus : public Unary { (instanciales) (instanciales) public: Uniminus(Expr *e) : Unary(e) {} int eval() const {return -op->eval();} }; class Mult : public Binary { public: Mult(Expr *e1, Expr *e2) : Binary(e1, e2) {} int eval() const {return op1->eval() * op2->eval();} }; // ... Autres classes concrètes 193 C++ de base ©2013 — Jean-Paul Rigault Classe Expr Résolution des fonctions virtuelles (2) Un appel de fonction virtuelle ne peut être résolu dynamiquement que lorsque la fonction-membre est invoquée à travers un pointeur (ordinaire ou smart) ou une référence Exceptions au typage dynamique Invocation à travers une instance Uniminus u(e); int r = e.eval(); // Type connu à la compilation // Uniminus::eval() Invocation avec nom qualifié (avec opérateur ::) class A { public: virtual void f() {...} }; class B : public A { public: virtual void f() {A::f();} // A::f(), quoi d'autre ? }; Invocation dans le corps d'un constructeur ou d'un destructeur (diapo suivante) ©2013 — Jean-Paul Rigault 194 C++ de base Classe Expr Résolution des fonctions virtuelles (4) Principe de résolution dynamique des fonctions virtuelles Un appel de fonction virtuelle dans le corps d'un constructeur (ou d'un destructeur) class B : public A { int *_p; public: virtual void f() { *_p = 10; } B() : _p(new int(0)) {} }; class A { public: virtual void f() {...} A() {...; f(); ...} }; Pointeur sur vtab (vtab_ptr) Table des fonctions-virtuelles de la classe (vtab) Index de la fonction virtuelle (connu à la compilation) Code de la fonction Instance L'initialisation de p a lieu après l'éxécution du constructeur de A Invoque toujours A::f() C++ de base 195 ©2013 — Jean-Paul Rigault Structure de données associée à chaque classe (polymorphe) C++ de base 196 ©2013 — Jean-Paul Rigault Classe Expr Fonctions virtualisables Classe Expr Conversions des classes polymorphes (1) Seule une fonction-membre peut être virtuelle Les conversions d'héritage (dérivée ➝ base) sont, comme toujours, des conversions implicites Une fonction amie ne peut être virtuelle • du moins dans la classe dont elle est amie (friend) Les conversions dans l'autre sens (downward cast) peuvent être incorrectes Les constructeurs ne peuvent être virtuels En revanche le destructeur peut être virtuel et devrait l'être pour les classes abstraites class Expr { virtual int eval() const = 0; virtual ~Expr() {}; };! class Unary : public Expr { ~Unary() {delete op;} };! class Binary : public Expr { ~Binary() {delete op1; delete op2;} }; C++ de base 197 Le destructeur peut être virtuel, mais pas virtuel pur class A { ... }; class B : public A { ! ! void f() { ... } // f() pas défini dans A }; A *pa = new B(); pa->f(); Ne compile pas ! ((B*)pa)->f(); static_cast<B *>(pa)->f(); ©2013 — Jean-Paul Rigault C++ de base Ces deux lignes compilent mais ne sont pas sures 198 ©2013 — Jean-Paul Rigault Classe Expr Conversions des classes polymorphes (2) Classe Expr Conversions des classes polymorphes (3) Opérateur dynamic_cast dynamic_cast permet un cast descendant très sûr, mais... Permet de s'assurer de la validité d'un « cast descendant » à l'exécution • conversion de pointeurs B *pb = dynamic_cast<B *>(pa); if (pb != nullptr) pb->f(); conversion de référence try { dynamic_cast<B&>(pa)->f(); } catch(std::bad_cast) { ! cerr << "bad conversion" << endl; } conversion de shared_ptr shared_ptr<B> pb = std::dynamic_pointer_cast<B>(pa); if (pb) pb->f(); // if (not pb.empty()) • • C++ de base 199 ©2013 — Jean-Paul Rigault Limitations de l'opérateur dynamic_cast Est utilisable seulement pour des classes polymorphes • (au moins une fonction virtuelle, pure ou non) Se comporte lui-même comme une fonction virtuelle • donc, résolu statiquement dans le corps d'un constructeur ou d'un destructeur class A { Toujours faux ! A() { if (dynamic_cast<B *>(this)) ... } }; class B : public A { ... }; C++ de base 200 ©2013 — Jean-Paul Rigault Classe Expr Conversions des classes polymorphes (4) Cours C++ de base Utilisation de l'opérateur static_cast On peut utiliser l'opérateur static_cast pour convertir des classes dans une hiérarchie d'héritage Cependant cet opérateur n'utilise que les informations disponibles à la compilation Conversion ascendante : dérivée ➝ base Il s'agit d'une conversion implicite, donc ne nécessitant que des informations de compilation partie 5 Résumé des mécanismes fondamentaux de C++ static_cast est 100% sûr, dynamic_cast est inutile Conversion descendante : base ➝ dérivée Ici il faut être « sûr de son coup », par construction En cas de doute, utiliser dynamic_cast static_cast inutilisable en cas d'héritage (multiple) virtuel 201 C++ de base ©2013 — Jean-Paul Rigault 202 C++ de base Résumé des mécanismes fondamentaux de C++ Cycle de vie des objets CRÉATION Cycle de vie des objets { Allocation Initialisation Membres statiques de classe Exceptions Utilisation Classes templates Surcharge des opérateurs et des fonctions DESTRUCTION C++ de base 203 ©2013 — Jean-Paul Rigault ©2013 — Jean-Paul Rigault C++ de base { Finalization Déallocation 4 modes d'allocation ; Durée de vie ; Portée Constructeur, construction par défaut Copie des objets : •initialisation et affectation •passage d'arguments et retour de fonction •copie par défaut Destructeur, destruction par défaut 4 modes de déallocation 204 ©2013 — Jean-Paul Rigault Destructeur, destruction par défaut Cycle de vie des objets Allocation, durée de vie, et portée (1) Où ? Durée de vie Bloc destruction automatique Portée (visibilité) Objets (à allocation) automatique Pile Objets (à allocation) statique Segment Toute l'exécution de destruction automatique données Bloc (static) Classe (static) Unité de compilation (static) Tout le programme (extern) Objets (à allocation) dynamique Tas (heap) Portée des pointeurs sur l'objet (pas de portée intrinsèque) De new à delete pas de destruction automatique Objets (utilisé comme) Tas De throw au dernier exception (heap) ? catch destruction automatique C++ de base 205 Bloc Clauses catch ©2013 — Jean-Paul Rigault Cycle de vie des objets Constructeur : garantie d'initialisation (1) Cycle de vie des objets Allocation, durée de vie, et portée (1) // File f1.cpp extern int glob; static double PI = 3.14; // File f2.cpp extern string *f(); int glob = 12; string *f() { double x; static int st = 10; if (...) throw Exc(); return new string(); } void g() { try { string *ps = f(); // ... delete ps; } catch (Exc& e) { cout << e.what() << endl; } } automatically allocated statically allocated dynamically allocated thrown (exception) ©2013 — Jean-Paul Rigault 206 C++ de base Cycle de vie des objets Constructeur : garantie d'initialisation (2) Initialisation par défaut des types de base Si une classe a des constructeurs, l'un de ceux-là doit être invoqué lors de la création d'un objet Immédiatemment après l'allocation Si un objet est construit sans paramètre de construction, la classe correspondante doit être constructible par défaut soit elle dispose d'un constructeur par défaut (sans argument) soit elle n'a aucun constructeur et ses classes de base et ses membres propres sont constructibles par défaut • • C++ de base Objets non instances de classe La garantie d'initialisation n'est que partielle pour ces objets • • • Constructeur par défaut des types de base Un tel constructeur existe ... mais n'est pas invoqué par défaut int f() { int x; int y = int(); // ... } alors le compilateur générera le constructeur par défaut cette génération peut échouer 207 ©2013 — Jean-Paul Rigault Objets à allocation automatique et dynamique : aucune initialisation Objets à allocation statique : initialisation à 0 Objets exception : aucune initialisation C++ de base x non initialisé y initialisé à 0 208 ©2013 — Jean-Paul Rigault Cycle de vie des objets Constructeur : garantie d'initialisation (3) Cycle de vie des objets Constructeur : garantie d'initialisation (4) Toutes les instances de classes ont la garantie d'initialisation, quel que soit leur mode d'allocation Objets à allocation statique : au début de l'exécution, avant main() Ordre de construction (hors héritage virtuel) • C++ ne garantit aucun ordre d'initialisation des objets à allocation 1. Les classes de base immédiates, dans l'ordre de la définition Objets à allocation automatique : lors de leur définition dans le bloc 3. Le corps du constructeur statique à travers les différentes unités de compilation 2. Les membres propres de la classe, dans l'ordre de la définition Objets à allocation dynamique : après l'appel de operator new Objets exception : après l'appel de throw 209 C++ de base ©2013 — Jean-Paul Rigault Cycle de vie des objets Destructeur : finalisation Copie à l'initialisation Destructeur par défaut A a1, a2(a1); // A a2 = a1; Si une classe n'a pas de destructeur, C++ le génère (ou tente de le faire) corps vide appel des destructeurs des membres propres appel des destructeurs des classes de base immédiates Copie à l'affectation Objets à allocation statique : à la fin de l'exécution, dans exit() A& A::operator=(const A&); Objets à allocation dynamique : par appel explicite de operator delete Objets exception : lors du dernier catch L'appel des destructeurs se fait dans l'ordre inverse de celui des constructeurs 211 A a1, a2; a2 = a1; Gérée par l'affectation de copie Ordre non garanti à travers unités de compilation Objets à allocation automatique : à la fin de leur bloc C++ de base Gérée par le constructeur de copie A::A(const A&); Toutes les instances de classe ont la garantie de finalisation, sauf les objets alloués dynamiquement • ©2013 — Jean-Paul Rigault Cycle de vie des objets Utilisation : copie des objets (1) Le destructeur réalise les « dernières volontés » d'un objet 1. 2. 3. 210 C++ de base ©2013 — Jean-Paul Rigault Ces opérations sont automatiquement générés par le compilateur si absent Copie membre à membre C++ de base 212 ©2013 — Jean-Paul Rigault Cycle de vie des objets Utilisation : copie des objets (2) Passage de paramètre par valeur void f1(A a); A aa; f1(aa); Retour par valeur aa est copié dans a par le constructeur de copie Passage de paramètre par référence sur une constante void f2(const A& a); A aa; f2(aa); aa et a désignent le même objet dans le corps de f2. Aucune copie. aa (et donc a) ne sont pas modifiables dans f2 Passage de paramètre par référence sur une variable void f3(A& a); A aa; f3(aa); aa et a désignent le même objet dans le corps de f2. Aucune copie. aa (et donc a) sont modifiables dans f3 213 C++ de base Cycle de vie des objets Utilisation : copie des objets (3) ©2013 — Jean-Paul Rigault A g1(); A aa = g1(); Retour par référence sur une constante const A& g2(); const A& aa = g2(); A& g3(); A& aa = g3(); aa désigne le même objet que celui retourné par g2. Aucune copie. aa est modifiable... A a; g3() = a; L'appel de g3() peut donc apparaitre à gauche d'une affectation (lvalue) 214 C++ de base ©2013 — Jean-Paul Rigault Membres statiques de classe Membres de données statiques Pointeur ou référence ? Communs à toutes les instances de la classe Une référence se comporte comme un pointeur constant, non nul Dans la portée de nommage de la classe Passage de paramètre On peut pratiquement toujours remplacer le passage par valeur par le passage par référence sur une constante Ceci est souhaitable pour passer de gros objets, afin d'en éviter la copie Ceci est moins désirable pour de petits objets (entiers, réels, pointeurs...) Retour de fonction Lors du retour par référence, l'objet retourné doit avoir une durée de vie supérieure à la durée de l'exécution de la fonction Ce ne peut donc pas être une variable locale de la fonction En conséquence, il n'est pas toujours possible de remplacer un retour par valeur par un retour de référence Heureusement les compilateurs peuvent souvent optimiser le retour de fonction par valeur C++ de base aa désigne le même objet que celui retourné par g2. Aucune copie. aa n'est pas modifiable Retour par référence sur une variable Cycle de vie des objets Utilisation : copie des objets (4) • La valeur retournée est copiée dans aa par le constructeur de copie 215 ©2013 — Jean-Paul Rigault Respectent le contrôle d'accès Fonctions-membres statiques Ne manipulent que des variables globales et d'autres variables statiques de la même classe Accès aux membres statiques class A { static int _sta; public: static int get_sta() const {return _sta;} }; cout << A::get_sta() << endl; C++ de base 216 ©2013 — Jean-Paul Rigault Membres statiques de classe Initialisation des membres de données static // File A.h Simples déclarations Rien n'est alloué et ne peut donc être initialisé class A { static int _sta; static double _stx; static string _sts; static vector<int> _stv; // ... }; Définitions de statiques Exceptions L'encapsulation rend un mécanisme d'exception quasi obligatoire En effet, que faire quand une condition anormale est détectée dans la partie cachée d'une classe, et ne peut pas y être réparée ? Notifier l'appelant (vraisemblablement responsable de l'erreur) Lui laisser la décision de continuer ou non // File A.cpp Initialisation de stx à 0 (0.0) #include "A.h" int A::_sta = 42; double A::_stx; string A::_sts("hello"); vector<int> A::_stv; // ... Initialisation de _stv par constructeur par défaut Respecter la sémantique de construction/destruction des objets locaux en C++ • Le fonctions C setjmp()/longjmp() ne peuvent pas être directement utilisées, car elles ignorent tout des destructeurs de C++ ©2013 — Jean-Paul Rigault 217 C++ de base Séparer syntaxiquement le traitement normal du traitement des erreurs Exceptions Mécanisme de base void f() { ! try { ! ! ! ! } Seul le mode de création (throw) distingue les exceptions ! g(); } catch (E1) {...}; catch (E2) {...} Quand une exception est lancée (throw), la fonction courante est abandonnée et l'exception est propagé à l'appelant void g() { ! try { ! ! ! ! h(); ! } ! catch (E1) {...} } void h() { ! ! if (...) throw E1(); ! else throw E2(); } C++ de base 50 219 ©2013 — Jean-Paul Rigault Exceptions Destruction des objets locaux Une exception est un objet de n'importe quel type copiable main() {f();} 218 C++ de base main() {f();} void f() { ! try { ! ! string s1; ! ! g(); ! } destruction of s1 ! catch (E1) {...}; ! catch (E2) {...} } Lorsqu'une fonction est abandonnée lors de la propagation d'une exception, tous les objets locaux automatiques sont correctement détruits La propagation s'arrête avec le premier bloc try qui posséde une clause catch correspondant au type de l'exception void g() { ! try { ! ! string s2; ! ! h(); ! } destruction of s2 ! catch (E1) {...} } La clause catch (handler) est alors exécutée ; ensuite le controle est transferré à la fin du block try, et de tous ses catchs void h() { ! string s3; ! if (...) throw E1(); destruction of s3 ! else throw E2(); } ©2013 — Jean-Paul Rigault donc avec appel de leur destructeur destruction of s2 Rien de tel ne se passe pour les objets à allocation dynamiques C'est la responsabilité du programmeur d'assurer leur destruction L'utilisation de smart pointers (shared_ptr, unique_ptr) simplifie le problème destruction of s3 C++ de base 51 50 220 ©2013 — Jean-Paul Rigault Exceptions Continuation forcée de la propagation main() {f();} Une exception capturée dans une clause catch peut être repropagée au niveau supérieur void f() { ! try { ! ! ... ! ! g(); ! } ! catch (E1) {...}; } void g() { ! try { ! ! ... ! ! h(); ! } ! catch (E1) {...; throw;} } Si une exception n'est jamais capturée rethrow void h() { ! if (...) throw E1(); } C++ de base 221 Classe templates Principes de base Possibilité de créer des fonctions ou des classes paramétrées, principalement par des types elle traverse main() Résolution purement statique (à la compilation) elle déclanche l'appel de la fonction prédéfinie terminate() (qui avorte le programme) Une sorte de macro-génération dirigée par les types ©2013 — Jean-Paul Rigault Classe templates Fonction template: définition Une seule représentation pour un ensemble d'opérations similaires sur des types différents ! template <typename T> ! inline const T& Min(const T& a, const T& b) { ! return a < b ? a : b; ! } Les contraintes sur les paramètres templates sont implicites (duck typing) • T représente ici n'importe quel type de C++ (classe ou type • C++ de base fondamental)... ... du moment que ce type dispose de l'opérateur < 223 ©2013 — Jean-Paul Rigault ©2013 — Jean-Paul Rigault 222 C++ de base Classe templates Fonction template: utilisation Utilisation comme une fonction à surcharge paramétrée ! ! ! ! int i, j; double x, y; j = Min(i, 3); y = Min(x, y); Déduction de type : T = int Déduction de type : T = double La résolution de surcharge est modifiée pour prendre en charge la déduction de type (voir plus loin) • Les conversions implicites disponibles sont limitées Instanciation forcée y = Min<double>(i, 3.75); Toutes les conversions implicites sont autorisées • C++ de base 224 ©2013 — Jean-Paul Rigault Classe templates Class template: définition (1) Classe templates Class template: definition (2) L'ensemble de la définition d'une classe doit être accessible au compilateur Une seule représentation pour un ensemble de classes similaires mais opérant sur des types différents template <typename Elem> class List {...}; template <typename Elem, int N> class Fixed_Array { ! Elem tab[N]; N doit être une constante de nature entière, dont la valeur doit ! // ... être déterminable à la }; compilation ©2013 — Jean-Paul Rigault 225 C++ de base Classe templates Class template: instanciation La définition de la classe La définition des fonctions-membres de la classe (leur corps) Le plus simple est de tout mettre dans le fichier d'entête (.h ou .hpp) template <typename Elem> class List { public: List(); void append(const Elem& e); void prepend(const Elem& e); // ... }; template <typename Elem> void List<Elem>::List() { // ... } template <typename Elem> void List<Elem>::append (const Elem& e) { // ... } template <typename Elem> void List<Elem>::prepend (const Elem& e) { // ... } 226 C++ de base ©2013 — Jean-Paul Rigault Surcharge des opérateurs et des fonctions Principe de résolution (1) L'nstanciation d'une class template est toujours explicite En C++ 2003, il faut écrire List<List<int> > l2; List<int> l1; List<List<int>> l2; Étant donné un appel de fonction Fixed_Array<double, 1000> fa1; Fixed_Array<List<int>, 100> fa2; ... f(a1, a2, ..., an) ... déterminer une unique fonction f (si elle existe) correspondant à l'appel List<Fixed_Array<List<char>, 10> > l3; • n connu, de même que les types T , T , ..., T typedef List<int> List_int; List_int l4; Deux instances d'une même classe template définissent des types différents Il n'y a aucune conversion implicite entre instances d'une même classe template • • C++ de base int est implicitement convertible en double, mais vector<int> n'est pas implicitement convertible en vector<double> Un ensemble de Français n'est pas un ensemble d'Humains ! 227 Résolution de surcharge ©2013 — Jean-Paul Rigault • • C++ de base 1 2 n des paramètres effectifs Le context de l'expression et le type de retour de la fonction ne jouent aucun rôle dans la résolution Le type de retour est seulement utilisé, après résolution, pour déterminer le type de l'expression correspondant à l'appel de f 228 ©2013 — Jean-Paul Rigault Surcharge des opérateurs et des fonctions Principe de résolution (2) 1. Identifier les fonctions candidates Surcharge des opérateurs et des fonctions Principe de résolution (3) Relation d'ordre sur les conversions implicites (force de conversion) Toutes les fonctions portant le même nom et visible au point d'appel 1. Correspondance exacte (pas de conversion) Utilise la résolution de nom (name lookup) 2. Ajustement de qualification Traitement spécial en cas de fonctions templates (voir plus loin) 2. Parmi les fonctions candidates, sélectionner celles qui sont viables Seulement les fonctions pouvant correspondre à l'appel Il doit exister une conversion implicite entre chaque paramètre effectif et le paramètre formel correspondant 3. Parmi les fonctions viables, sélectionner l'unique meilleure correspondance, si elle existe Repose sur une relation d'ordre entre les fonctions viables, induite elle-même par une relation d'ordre sur les conversions implicites de paramètres 229 ©2013 — Jean-Paul Rigault Surcharge des opérateurs et des fonctions Cas particuliers des fonctions templates Une fonction template peut être utilisée comme une fonction à surcharge paramétrée Le compilateur doit alors déduire le type des paramètres templates template <typename T> T min(const T&, const T&); int i, j; Déduction de type : T = int auto m = min(i, j); Il serait trop compliqué (et non intuitif) de tenir compte de toutes le conversions possibles Lors de cette déduction de type, C++ ne considère que les conversions suivantes • • • correspondance exacte ajustement de qualification (ajout de const ou volatile) conversions d'héritages Échec de la déduction de type auto m = min(i, 3.75); auto m = min<double>(i, 3.75); OK : type forcé double C++ de base 231 ©2013 — Jean-Paul Rigault Ajout de const ou volatile 3. Promotions • Elles doivent avoir le bon nombre de paramètres C++ de base • Conversions préservant la valeur entre entiers, entiers et réels, réels 4. Autres conversions standards • • • Autres conversions entre entiers et réels Conversions de pointeurs Conversions d'héritage 5. Conversions définies par l'utilisateur : constructeur, opérateur de cast • C++ de base Au plus une par argument 230 ©2013 — Jean-Paul Rigault