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