ou comment être les stars du C++
Raffi Enficiaud
INRIA
16-18 février 2009
INRIA - IMEDIA
Formation C++ avancée
Organisation générale
A qui s’adresse-t-elle ?`
vous permettre d’être le plus autonome possible avec le C++
I Mieux comprendre certaines ”subtilités”du langage
I Mieux comprendre ce que génère le compilateur à partir du code que vous
écrivez
vous rendre plus productif avec les mécanismes du C++
I Utiliser le compilateur pour faire des évolutions incrémentales
I Travailler en équipe
I Identifier plus précisément les sources potentielles de bug
I Mettre en place des mécanismes de controˆle de fonctionnalité et d’évolution
I S’intégrer au mieux à un ”monde extérieur”à votre (partie de) programme
Déroulement
Formation C++ avancée
J1 : C++ avancé
Contenu
Quelques difficultés du langage
Rappels sur les instructions
Classes
Héritage
Exceptions structurées
Etude de cas
Subtilité ?
Exemples
Syntaxe pas trop difficile
Langage difficile
Types
struct union
POD
typedef extern
Référence
Garantie de non modification : const
Espace de nom
Pointeurs sur fonction Références sur fonction volatile
Même là-dessus il faut revenir
Plusieurs types de chaˆ?ne de caractère : Déclaration :
Chaˆ?ne de type ”classique”: char
Chaˆ?ne de type étendu ”international”: wchar t
Le type international est souvent utile pour ce qui concerne le système de fichier, les messages utilisateurs, etc
1 char ?s constant = "blablabla" ; 2 wchar t? s wide = L"wide blablabla" ;
Préprocesseur :
Concatène automatiquement les chaˆ?nes de caractères qui se suivent :
1char? toto = "One big string split " "among small strings"
2" and among many lines"
3" " ;
Mot clef ”enum”
Structures
Par compatibilité avec le C, l’accès par défaut est public (cf. partie sur les classes)
Union
Les types ”natifs”ou agrégation de type natif
Plus précisément, tout type ayant un équivalent direct avec le C, concernant l’initialisation, la taille, et l’adressage :
toute valeur numérique (bool, int, double ) les enums et les unions les structures sans aucun constructeur, opérateur d’attribution, classe de base, méthode virtuelle, variable non-statique non publique, etc.
Généralement, les typedefs permettent d’avoir un code plus concis, et dans certains cas d’accélérer les temps de compilation (template).
Nous verrons quelques utilisations du mot clef ”typedef”pendant la partie sur les templates.
Mot clef ”extern”
Boucles ”for”
1 for ( int i = 0; i < 100; i++)
La variable ”i”est interne au bloc for. Les ”;”définissent les champs : il est possible de ne pas les initialiser :
1int i = 0;
2for ( ; i < 100; i++)
Pointeurs
Définition
const”
Définition
Nous verrons plus tard que, pour un objet, seules des méthodes assurant la constance de l’objet peuvent être appelées.
const”
Utilisation des pointeurs
namespace”
Définition & syntaxe
namespace”
Usage
Rappels
Permet de stocker l’adresse d’une fonction dans une variable (l’attribution de cette variable pouvant être donné par une fonction).
La variable peut ensuite (bien-suˆr) être utilisée pour appeler la fonction sur laquelle elle pointe.
1bool func2 ( const double&, int , f l o a t &) ;
2void func1 ( bool (? toto ) ( const double&, int , f l o a t &)) {
3// Déclaration de la v a r i a b l e toto , comme étant un pointeur
4// sur une fonction de type bool ( const double&, int , f l o a t &)
5 f l o a t returne d f ;
6
7 i f ( toto (0.39 , 42 , re turned f ) )
8 std : : cout << "function returned true and the float is = to" << returne d f << std : : endl ;
9else
10std : : cout << "function returned false" << std : : endl ;
11}
12func1 (0) ; // e rreu r à l ’ exécution ( l o r s de l ’ appel à toto ) : fonction n u l l e
13func1(&func2 ) ; // a t t r i b u t i o n de toto à func2
Permet de stocker la référence d’une fonction dans une variable (l’attribution de cette variable pouvant être donné par une fonction).
Contrairement au pointeur, la référence sur fonction est non-mutable (ré-assignation de la référence impossible).
1bool func2 ( const double&, int , f l o a t &) ;
2void func1 ( bool (&var ) ( const double&, int , f l o a t &)) {
3// Déclaration de la v a r i a b l e toto , comme étant une ré fé re nc e
5 f l o a t returne d f ;
6
7 i f ( var (0.39 , 42 , returned f ) )
8 std : : cout << "function returned true and the float is = to" << returne d f << std : : endl ;
9else
10std : : cout << "function returned false" << std : : endl ;
11}
12func1 (0) ; // e rreu r à la compilation : fonction n u l l e n ’ e x i s t e pas
13func1 ( func2 ) ; // a t t r i b u t i o n de toto à func2
Ce modificateur a été initialement utilisé pour permettre des accès à des variables externes au processeur sur lequel le programme s’exécute (chip annexe par exemple). Il indique au compilateur, en outre, que la variable en question ne participe pas à des optimisations (inférences sur sa valeur).
Pour ceux/celles qui utilisent les threads, ce modificateur doit être utilisé pour les variables dont l’accès se fait via des threads concurrents.
Fonction retournant un répertoire temporaire
On souhaite avoir une fonction, temporary path, qui retourne une chaˆ?ne représentant le chemin complet d’un répertoire temporaire.
On souhaite en outre :
que le chemin temporaire soit lu à partir de la variable d’environnement ”TEMP”. Pour cela, on utilisera la fonction C/ANSI ”std :: getenv”(dans ”<cstdlib>”) qui prend en paramètre le nom de la variable d’environnement et qui retourne ”char ?”. Ce retour peut valoir ”NULL”si la variable d’environnement n’existe pas.
on souhaite que cette variable ne soit lue qu’une seule fois, de manière à réduire l’accès ”couˆteux”à l’environnement.
temporary path () = "mon_nouveau_repertoire" ;
Indice : il s’agit d’une initialisation de type statique, à l’aide d’une fonction retournant la chaˆ?ne qu’on souhaite.
Enficiaud (INRIA) C++ 16-18/02/2009
Attributs membres
Méthodes membres Attributs statiques
Méthodes statiques
Attributs mutables Champs membres this
ClassesPointeur sur membres
Constructeurs de classe
Destructeurs
Surcharge d’opérateurs
Opérateurs arithmétiques
Opérateur d’attribution
Opérateur de conversion
Opérateur de fonction
Définition
Une classe déclare des propriétés communes à un ensemble d’objets. Elle définie des attributs (variables membres) représentant l’état des objets, et des méthodes définissant leurs comportements.
Une instance de classe est un objet suivant la définition de la classe, et dont les variables membres ont une valeur.
Définition (2)
La classe est l’élément central de la programmation orientée objet. L’objet est une instance de classe.
Déclaration & Syntaxe
La déclaration est donné par la directive ”class”ou ”struct”
Elle est suivie éventuellement du corps de la classe (accolades) Elle se termine par un ”;”
Si le corps est défini, il est possible de déclarer des objets :
1cl a s s A {} a , b , c ;
2struct B {/? . . . ?/} d , e , f ;
Si le corps n’est pas défini, la directive déclare une classe dont le corps est inconnu. Puisque le corps est inconnu, il est impossible de déclarer des objets de cette classe :
2A a ; // erre ur : la t a i l l e de a est inconnue
3A ?a ; // ok : la t a i l l e du pointeur et son type sont connus
Le corps de la classe définie ses membres. Une fois le corps terminé, il n’est pas possible d’ajouter des membres.
Attributs membres
Il est possible de définir les méthodes membres à l’intérieur du corps de la classe :
il deviennent alors ”inline ”(déclarable dans un header par exemple).
Attributs static
Variable membre non liée à une instance particulière, et partagée par toutes les instances d’une classe.
Il faut l’initialiser de la même manière qu’une variable statique normale, et en dehors du corps de la classe
Sauf pour un entier constant, ou un enum, qui peut être initialisé à l’intérieur du corps de la classe
La syntaxe impose la déclaration dans le corps de la classe, et l’attribution/initialisation à l’extérieur du corps de la classe. Attention aux accès concurrentiels.
Attributs ”mutable”
La modification d’une variable mutable est autorisée à partir d’une méthode ”const”.
Les variables ”mutable”permettent de cacher certains détails d’implémentation (ex. mécanisme de cache).
Controˆle d’accès
Dans le corps de la classe, on attribut à chaque membre une restriction sur l’accès, parmi :
public : le membre est visible de ”tout le monde” protégé (protected) : le membre n’est visible que d’une classe fille (cf. héritages)
privé (private) : le membre n’est visible que d’une instance strictement du même type (qui peut être l’instance courante)
Par défaut, une ”class”a une visibilité privée, alors qu’une ”struct”a une visibilité publique (compatibilité avec le C).
1 cl a s s A {
5 int i p u b l i c ;
6 void method () ;
7 } ;
Champs membres
Les champs de type sont valides à l’intérieur de l’espace de nom de la classe. Rappel : les champs de type ne prennent aucune place en mémoire, et donc ne participent pas à la taille occupée en mémoire par la classe.
Mot clef ”this”
Nous verrons ce dernier point pendant les constructeurs.
Décomposition en nombres premiers
Avoir une classe, ”decomposed primes”, décomposant un entier en puissances de nombres premiers.
1 un champ ”return type”précisera le type de stockage. Il sera du type ”map”
2 la méthode ”decompose”prendra un entier en entrée, et retournera ”return type”
3 une liste initiale de nombre premiers sera définie au lancement du programme (note : variable statique)
4 la classe aura une ”mémoire”cachée, lui permettant d’ajouter des nombres premiers à sa liste initiale
5 pour plus de performances, la classe stockera la décomposition en nombre premiers des nombres inférieurs à 100.
On se servira du type ”caché”, ”std :: map<int,int>”(tableau associatif), pour stocker une décomposition. On se servira du type ”caché”, ”std :: set<int>” (ensemble ordonné), pour stocker les nombres premiers dans l’ordre croissant.
On s’aidera des fichiers ”decomposed primes.[h|cpp]”.
Syntaxe
Les pointeurs sur membre non static doivent indiquer qu’ils pointent à l’intérieur d’une classe. Il doivent donc spécifier le bon espace de nom.
1struct A {
2int i , j , k , l , m, n ;
3int methode ( f l o a t ) ;
4int methode2 ( f l o a t ) ;
5};
7 int A::? p i = &A : : i ; // pointeur ”p i ” sur un e n t i e r dans A, i n i t i a l i s é sur l ’ adresse de i
8int (A::?) (f l o a t ) ; // pointeur sur une méthode de A ( retournant int , et prenant un argument f l o a t )
9 int (A::? p methode ) ( f l o a t ) = &A : : methode2 ; // pointeur p methode i n i t i a l i s é sur A : : methode2
S’ils pointent sur un membre static, ils sont absolus, n’ont pas besoin de spécifier l’espace de nom, et n’ont pas besoin d’une instance pour être déférencés.
S’ils pointent sur un membre non static, ils sont valident naturellement à travers une instance (nécessaire pour déférencer le pointeur).
Syntaxe (2)
Syntaxe (3)
Rappel
Construction des variables membres
Rappel : les méthodes ne sont pas des variables (et donc n’influencent pas la taille de la classe).
Construction des variables membres (2)
Il existe bien des manières d’initialiser un objet
Controˆle d’accès
Les mêmes règles d’accès s’appliquent au constructeur :
Exercice
”Copy constructible”(2)
Variable membres const
Variable membres référence
Les références doivent être initialisées dans la liste d’initialisation.
Les ”variables”références étant constantes, elles doivent être initialisées dans la liste constructeur également.
Restriction des conversions : mot clef ”explicit”
Aucun ”trans-typage”(conversion implicite) n’est effectué pour les constructeurs explicites.
1struct A {
3e x p l i c i t A( long , const double&) ;
4};
Il porte le même nom que la classe, et n’a jamais de paramètres en entrée. Il faut l’appeler avec l’opérateur ”delete”et (pratiquement) jamais directement.
Les objets sont détruits dans l’ordre inverse de leur déclaration lors de l’accolade fermante du destructeur.
Classe de matrice
Ecrire une classe de matrice ”matrice d”de type double. Le constructeur de la classe aura pour paramètres deux entiers indiquant la taille (allocation dynamique).
Introduction
Lorsqu’on défini l’opérateur d’une classe, on dit généralement qu’on surcharge cet opérateur. Ce sous-entend que si ces opérateurs ne sont pas définis, le compilateur essaie de les générer automatiquement. Plusieurs opérateur d’intérêt, exemple :
1operator= ; // a t t r i b u t i o n
2operator== ; operator!= ; operator <; operator> ; . . . // comparaison
3operator++ ; operator?? ; // auto incrémentation et décrémentation ( pré ou post )
4operator+ ; operator+=; operator? ; operator?=; . . . // arithmétique
5operator? ; // déférencement ou m u l t i p l i c a t i o n
6operator ! ; // négation
Classes & surcharge d’opérateurs
Opérateurs arithmétiques
Pour une matrice :
Ecrire les opérateurs d’addition, de multiplication avec un double
Ecrire l’opérateur de multiplication avec une autre matrice
Ecrire l’opérateur d’addition entre une décomposition et un entier
Ecrire l’opérateur de multiplication (deux ”decomposed primes”et un entier)
Ecrire l’opérateur de division (deux ”decomposed primes”et un entier) : on se permet d’avoir des exposants négatifs
Définition & syntaxe
Cet opérateur est utile lorsque la copie n’est pas triviale (ie. typiquement avec membres de type pointeurs et/ou références). Souvent, lorsque l’opérateur ne peut être généré automatiquement, le compilateur émet un avertissement (warning).
Transitivité
Remarque : auto-attribution (1)
Auto-attribution (2)
Donc
gérer explicitement le cas a = a par ”this”
)
std : : s t r i n g ( r
changer l’ordre des opérations pour avoir une gestion implicite
)
s tmp = new std : : s t r i n g ( r . s ) ;
Code auto-généré
Le compilateur ”sait”faire des choses :
Si les types contenus dans la structure/classe sont de type triviaux ou possèdent tous un constructeur par recopie (défini ou trivial), alors il n’est pas nécessaire de définir l’opérateur d’attribution (même remarque s’applique au constructeur par recopie).
Donc :
N’écrire l’opérateur d’attribution seulement quand vous le jugez nécessaire (membres pointeur/référence/const).
Classe de matrice
Ecrire l’opérateur d’attribution de la matrice avec une autre matrice : si les matrices ne sont pas de même taille, alors on fait la recopie.
Ecrire l’opérateur d’attribution entre une matrice et une autre, entre une matrice et un entier.
Définition
Cet opérateur peut être très pratique dans certains cas. Il ne définit pas de type de retour, puisque ce type est implicite par le cast.
Contrôle d’accès
Visibilité & désambigu¨?sation
Construction et destruction
HéritageConversions
Héritage multiple
Classes virtuelles
Classes virtuelles pures
Interfaces
Définition
Que signifie héritage?
Controˆle d’accès
Controˆle d’accès (2)
Il est possible de se référer explicitement à une méthode ou un attribut de l’une des classes mères (si les contrôles d’accès le permettent), par la syntaxe suivante :
1 B : : value type doSthg () const {
2 A : : doSthg () ; // appelle A : : doSthg au l i e u de A : : doSthg
3 // do some other things
4 }
Constructeurs
Une classe fille contient la classe mère (c’en est une extension). Donc, si la classe fille est construite, il faut que la classe mère le soit également.
Le constructeur par défaut de la classe mère est appelé avant l’initialisation du premier (ie. de tous) membre de la classe fille.
La classe fille peut appeler un constructeur particulier de la classe mère, dans sa liste d’initialisation
En fait, la classe mère peut être vue (en terme d’implémentation) comme une variable membre avant toutes les autres.
Les contrôles d’accès du constructeur de la classe mère est identique à n’importe quelle autre membre
Constructeurs : exercice
Soit la classe mère suivante, ouvrant un fichier dans le répertoire courant :
1cl a s s F i l e U t i l { 2public :
4F i l e U t i l ( const std : : s t r i n g &filename ) {/? . . . ?/}
5
6/? . . . ?/
7};
Une fois le fichier ouvert, la classe propose des manipulations de ce fichier (or de propos ici).
Nous souhaitons une nouvelle classe héritant ces méthodes de manipulation, mais ouvrant les fichiers de manière différente.
Note : Nous considérerons que la classe mère stocke un handle de fichier dans la variable membre ”FILE ?m file handler”. Elle offrirait à ses classes filles un constructeur particulier
Destructeurs
Le destructeur de la classe mère est appelé après la destruction de tous les membre de la classe fille (lors de l’exécution de l’accolade fermante de la classe fille).
C’est à dire, dans l’ordre inverse de la création des éléments.
Conversions
Il est possible de couper une classe, mais il n’est pas possible d’inventer des extensions de classe.
Une classe fille est une extension de sa classe mère. Il est donc possible de présenter la partie concernant sa classe mère.
1 // A f i l l e de B
2 A a ;
3 B ?p b = &a ; // ”du plus p ré c i s au plus général ”
4 B& r e f b = a ;
L’inverse n’est pas vrai, car il faudrait inventer des données manquantes :
1 // B f i l l e de A
2 A a ;
4 B& r e f b = a ; // s i ces i n s t r u c t i o n s sont v a l i d e s (a p r i o r i e l l e s ne l e sont pas ) .
Conversions & dynamic cast
Il est possible de tester à l’exécution(et parfois à la compilation), à partir d’un pointeur ou d’une référence sur une classe, si une classe dérive ou est une classe mère d’une autre.
Plus précisément, le compilateur sait si une classe est mère d’une autre à la compilation, mais l’inverse n’est pas vrai. Le test doit être fait en runtime.
Définition
Le graphe d’héritage étant plus complexe, le compilateur peut se plaindre parfois de ne pas pouvoir résoudre un appel (deux classes mères ayant une méthode de même nom/paramètres). Il faut alors faire la désambigu¨?sation de la même manière que pour l’héritage simple.
Constructeurs & destructeurs
De la même manière que pour l’héritage simple :
Les classes mères sont toutes construites avant n’importe quel attribut membre de la classe.
Si aucun constructeur de classe mère n’est appelé dans la liste d’initialisation, le constructeur par défaut est appelé.
Les classes mères sont construites de gauche à droite dans la liste des classes de base (ex. précédent : A puis B).
et
Les classes mères sont détruites après destruction du dernier membre de la classe fille
Elles sont détruites dans l’ordre inverse de leur construction (de droite à gauche dans la liste des classes de base).
Définition
Une classe virtuelle est classe contenant au moins une méthode virtuelle
Méthode dont l’implémentation peut être (re)définie par une classe dérivée.
Le mot clef ”virtual ”indique si la méthode en question est virtuelle (ou pas). Une méthode virtuelle participe à la taille occupée en mémoire par la classe.
La méthode appelée par défaut (ie. sans spécification d’espace de nom) est toujours la plus profonde dans la hiérarchie des classes.
Classe virtuelle
Classes virtuelles
Utilisation
Classes virtuelles pures
Définition
Propriétés
Une classe mère peut donc ”forcer”l’implémentation (dans les classes filles) des méthodes qu’elle déclare pures
Héritage
Que se passe-t-il si delete est appelé à partir d’une la classe mère?
Définition
Concept général indiquant les méthodes pour accéder à un objet (au moins en exploiter certaines fonctionnalités) : l’interface définie la signature pour l’utilisation de ces fonctionnalités. La particularité importante de l’interface est le découplage de l’accès d’un objet et de son implémentation. Cette particularité fait en sorte que :
Les clients ne connaissent pas les détails d’implémentation, et ne sont donc pas soumis aux variations de celle-ci (concept objet).
L’interface est supposée stable en termes de fonctionnalités et signatures, notamment par rapport à l’évolution d’un composant logiciel.
L’approche par interfaces est souvent moins performante, puisque les signatures ne sont pas optimales pour une implémentation particulière (et que l’accès aux variables membres se fait par le biais de méthodes d’accès).
En C++, une interface est une classe ne définissant aucune variable membre et dont toutes les méthodes sont publiques et virtuelles pures.
Exemple
Définition & utilisation
ExceptionsDétailsExceptions & constructeurs structuréesExceptions & destructeurs
Exceptions standards
Définition
Une exception est un mécanisme spécial du C++, lancé par le mot clef ”throw”, et permettant d’arrêter l’exécution courante du programme jusqu’à une instruction de récupération d’exception (bloc ”try”/ ”catch”).
L’instruction ”throw”peut être suivie de n’importe quelle classe, structure, entier.
L’instruction ”catch”intercepte la même classe que celle du ”throw”.
Interceptions & relancement
Interception générale
Attention : puisque l’exception n’est pas nommée, il n’est pas possible d’avoir plus de précision sur l’exception.
Mais : Il est possible de relancer l’exception
Déroulement des interceptions
Déroulement des interceptions : détails
Voici ce qu’il se passe lorsqu’une exception est levée :
On arrête l’exécution du programme au point du ”throw”
Le programme teste s’il se trouve dans un bloc ”try”(si ce n’est pas le cas, fin de programme)
On cherche un bloc ”catch”compatible avec le type de l’exception levée (si aucun, fin de programme)
On crée une sorte de ”tunnel/point de connexion”avec ce bloc ”catch”, et on se place dans un mode privilégié
Dans ce tunnel, on appelle les destructeurs de tous les objets crées sur la pile, dans l’ordre inverse de leur création
On retrouve l’exécution du programme au niveau du bloc ”catch”
On appelle le processus de destruction des objets temporaires : le déroulement de pile (stack unwinding).
Utilisation
1 Retour de fonction (comme en Java)
2 Arrêt d’exécution sur faute ”grave”. Exemple : on appelle une fonction définie par certains critères sur les données qu’elle traite, et ces critères ne sont pas respectés.
1 Possibilité de marquer le type d’exception possible levée dans une fonction
2 Rapprochement du catch de là ou` l’exception est potentiellement levée
3 Utilisation d’un mot clef pour indiquer qu’un appel ne lève pas d’exception ou ne lève une exception que d’un certain type (non supporté par tous les compilateurs).
1void function1 () throw () ;
2void function2 () throw ( Exception3 ) ;
3void function2 () throw ( . . . ) ;
Cohabitation avec les constructeurs
En effet, les constructeurs n’ont pas de valeur de retour (résultat = objet construit). Lever une exception dans le constructeur en cas de problème permet :
Lorsqu’une exception est levée dans le constructeur, la classe n’est pas créée.
Les destructeurs des variables membres initialisés sont appelés
Le destructeur de la classe en cours de construction n’est pas appelé (la classe n’a pas été construite).
Les destructeurs des classes mères instanciées sont appelées (l’ordre d’instanciation joue un rôle important).
Cohabitation avec les destructeurs
Rappel : (quelques diapositives précédentes) le processus de stack unwinding est exécuté dans un mode particulier.
En fait, ce mode est particulièrement fragile, et ne supporte absolument pas un lancement d’exception.
Standard ”STL”
Le standard (fichier ”<stdexcept>”de la STL) défini quelques exceptions ”type”.
La classe de base est ”std :: exception”, qui implémente la méthode ”virtual const char ? std :: exception :: what() const”, donnant des renseignements ”utilisateurs”sur le message de l’exception.
2 std : : l o g i c e r r o r ; // reporte l e s e r r e u r s de logique d ’ appel
3 std : : invalid argument ; // argument i n v a l i d e d ’un appel
4 std : : out or range ; // reporte l e s dépassements : u t i l i s é dans c e r t a i n s appels STL ( substring , vector
. . . )
5 std : : bad alloc ; // reporte l e s problèmes d ’ a l l o c a t i o n mémoire ( dans header <new>)
Il faut généralement se reporter à la documentation pour connaˆ?tre les exceptions qu’on doit récupérer. Les exceptions font partie du comportement du service demandé : elles affectent le fonctionnement de VOTRE programme.
Exercice
Soit une classe ”ContenuFichier”qui contient le contenu d’un fichier. A son` instanciation, il ouvre le fichier, lit son contenu et le met dans un buffer, puis ferme le fichier.
Il ouvre le fichier à l’aide de la fonction ”fopen”.
Si le fichier n’existe pas, il lance une exception ”file not found ”. Cette exception est à définir. Elle doit contenir le nom du fichier recherché.
tant qu’il n’a pas terminé la lecture du fichier, il place le bloc précédemment lu à la fin d’un tableau. Pour cela, on utilisera un vecteur STL ”std :: vector” (”#include <vector>”) et sa méthode ”std :: vector :: push back”.
à la fin de lecture, il doit allouer un gros bloc mémoire, dans lequel il va copier les blocs précédemment lus et placés en mémoire.
Exercice (suite)
Etude de cas
Chaˆ?ne algorithmique
On veut un programme qui applique des algorithmes à la chaˆ?ne à une structure de donnée initiale. Chaque algorithme à une interface identique à tous les autres algorithmes. Il sera identifié dans la chaˆ?ne par son nom. Pour chaque chaˆ?ne algorithmique, chaque algorithme à un nom unique. Chaque algorithme ”A”peut créer une nouvelle donnée, qui pourra être utilisée dans la chaˆ?ne par les algorithmes en aval de ”A”. La donnée sera par exemple identifiée par son type exact et le nom de l’algorithme qui l’a créé. L’exercice est en deux phases :
Phase de réflexion/étude/design : à l’aide de mécanismes d’héritage et d’interfaces, définissez les intervenants informatiques. On fera un point sur le design.
Phase de développement : vous retroussez vos manches.
En 4 équipes.
Définition de l’interface d’un algorithme
Définition de la structure de donnée
Création de deux algorithmes
J2 : Programmation générique
Contenu
Les templates
STL
Patrons de conception (Design patterns) Introduction
Présentation
Mécanismes
Déclaration & définition
Les templatesEspace de nom
Inférence de type
Spécialisation
Programmation incrémentale
Synthèse
Egalement appelés ”patrons”
Les templates permettent de définir des familles de classes, structures ou fonctions.
Mécanisme très puissant!
2 permet d’ajouter des mécanismes logiques dans le choix des structures/fonctions invoquées (méta-programmation)
3 permet d’avoir un code facilement extensible à des nouveaux cas d’utilisation
Difficile à prendre en main
Certains compilateurs sont en retard dans le support des templates (il faut les bannir)
Exemple
Un exemple trivial de l’abstraction du type pour des fonctions
Exemple & remarque
La fonction maximum sera alors appelable pour tout type ”T”, MAIS avec les conditions suivantes :
l’algorithme est valide si l’opérateur ”>”existe pour le type ”T” la fonction prend des copies des objets et retourne une copie, ce qui n’est pas valide pour tout type ”T”:
1 l’opérateur par recopie est correct pour l’algorithme (il retient l’information permettant de faire la comparaison)
2 l’opérateur par recopie est correct pour la partie appelante (”return”ne retourne pas une référence, mais une copie temporaire)
On commence à comprendre pourquoi c’est si séduisant, mais un peu délicat?
Exemple (2)
Un exemple trivial de l’abstraction du type pour des structures
Détails
Il existe plusieurs mécanismes autour de l’utilisation des templates :
la déclaration et la définition la spécialisation totale ou partielle la déduction (inférence) de type pour les fonctions templates
Déclaration & définition
La déclaration commence par le mot clef ”template”, suivi de :
Les déclarations, classiquement, indiquent au compilateur que ces fonctions et ces structures templates existent.
Elles indiquent également le nombre d’arguments abstraits dans le template (utile pour la spécialisation ou pour les arguments templates).
Liste des types admissibles
Les types admissibles dans la liste des type sont :
Il est possible d’avoir, pour les classes et structures templates, des arguments templates par défaut (et qui peuvent référer à des types précédents)
1template <cl a s s T, cl a s s U = int , cl a s s V = T>
2struct ss t r u c t ;
3
4// même p r i n c i p e que pour la surcharge , V non optionnel ne peut s u i v r e un argument optionnel
5template <cl a s s T, cl a s s U = int , cl a s s V>
6struct ss t r u c t 2 ;
7
8template <cl a s s T, cl a s s U = int >
9T func1 (T, U) ; // i n t e r d i t ! pas de défaut sur l e s fonctions templates
Liste des types admissibles : valeurs
Liste des types admissibles : valeurs (2) Suite
Liste des types admissibles : template
Il est également possible de mettre parmi les arguments, un autre template. Il doit être déclaré précisément avec la même liste template que sa définition/déclaration.
Espace de noms & champs de type
Un peu le même principe que les namespace : utilisation de l’opérateur :: .
Dans un template, les types complets sont les types connus : soit parce que ce ne sont pas des types abstraits (constantes) soit parce qu’ils font partie de la liste d’arguments template a
a. les arguments templates sont également des types
Un nom dépendant est un type qu’il n’est possible de connaˆ?tre qu’une fois le type template complètement résolu.
Il n’est valide qu’à l’intérieur des templates.
Espace de noms & champs de type (2)
Exercice 1
S’abstraire des opérations de recopies dans le template suivant :
template <cl a ss T>
T maximum( const T i1 , const T i2 )
{
return i1 > i2 ? i1 : i2 ;
}
En faire un functor, qui sache renseigner ses types d’entrée et de sortie.
Exercice 2
Ecrire une structure template give me square, présentant une interface de tableau indicé ”operator []”, et retournant le carré de l’opérateur de fonction template (”functor”- ”operator()”) suivant :
1 template <
2 cl a s s storage type ,
3 int s i z e = 10 ,
4 cl a s s return type = float >
5 struct s my struct t {
6 storage type value1 [ s i z e ] ;
7 typedef return type return type ;
8
9 return type operator () () {
10 storage type ret = 0;
11 for ( int i = 0; i < s i z e ; i ++)
12 ret += value1 [ i ] ;
13 return ret / s i z e ;
14 }
15 };
Ce mini exercice doit montrer comment interfacer correctement deux templates.
Inférence de types
Inférence de types : exercice
La sortie de ”typeid”est compilateur dépendant! Il est utilisé pour tester l’égalité de types, mais non pour une sortie textuelle véritablement informative. ”typeid” retourne un objet CONSTANT ”type info”(inclure le fichier #include <typeinfo>).
Spécialisation
Spécialisation totale/partielle
Notion FONDAMENTALE !
La spécialisation est un mécanisme puissant qui relègue au compilateur les choix des structures lors de la découverte des paramètres templates.
La spécialisation définit des NOUVEAUX TYPES. Une classe template et ses spécialisations n’ont RIEN DE COMMUN a.
a. en termes de contenu
Cf. exemples précédents : les classes ”s my template<U,V>”et ” s my template<int, int>”sont des TYPES DIFFERENTS.
C’est un type de spécialisation ou` tous les types templates sont renseignés.
Cf. exemples précédents.
Spécialisation totale/partielle
Spécialisation & programmation incrémentale
Supposons que nous avons un functor ”s do very weird things ”qui travaille sur un ensemble de types ”Ti”
Supposons que maintenant, nous avons une version ”améliorée”de ce functor, pour une combinaison C de ces types
Alors, il est possible d’ajouter cette version ”améliorée”dans notre programme, sans ”rupture de contrat”pour les appels existants. Le compilateur utilise alors la version améliorée ou générale automatiquement, selon la combinaison de type.
On a appelle ce mécanisme la programmation incrémentale : on commence par une version très générale, et ensuite on ajoute le particulier, sans retoucher le code client existant.
a. enfin je
C’est donc le compilateur qui travaille plus que nous.
141 / 201
Functors - objets fonctionnels
<functional>
STL & conteneurs
STL<<vectormap>>
STL & algorithmes génériques
<algorithm>
Présentation
La STL (Standard Template Library) est une librairie standard, distribuée avec (pratiquement?) tous les compilateurs C++.
Elle fournit des services minimaux, comme des structures template de ”container” (éléments contenant), des algorithmes d’exploitation et des classes utilitaires de renseignement. Elle participe beaucoup à la création d’algorithmes templates.
Il faut très souvent faire appel à la STL
Utiliser la STL est bon pour votre productivité
Il s’agit donc d’un objet, qui est par définition plus riche qu’une fonction, et qu’il est possible d’appeler comme une fonction.
1 struct s functor dumb limited {
2 // ! Surcharge de l ’ opérateur operator () , induisant l e comportement dé s i ré 3 bool operator () ( const bool b) const throw () { return ! b ;}
4 };
5
6 s functor dumb limited op ;
7 a s s e r t (! op( true ) ) ;
Plus généralement, un functor permet de faire des choses plus puissantes que des fonctions.
<functional> (2)
”std :: bind1st”/ ”std :: bind2nd”: fonctions mettant un des deux paramètres d’une fonction binaire à une constante
Le functor résultant devient unaire.
Tableau dynamique
Bon, tout le monde connaˆ?t?
Tableau associatifs
Une ”map”est un tableau associatif : à chaque clef, elle associe une valeur.
Le type de la clef et celui de la valeur sont les deux paramètres templates obligatoires.
Un troisième paramètre très utile donne la fonction d’ordre sur la clef. Il vaut par défaut le functor ”std :: less ”(inférant un ordre ”<”sur l’espace des clefs).
L’ordre est donné par un functor, il est donc très facile d’en définir un sur une structure de clef complexe.
Exercice
Enoncé
Renseignements numériques
”<limits>”contient des informations relatives aux types numériques. Elle contient principalement une classe template ”T”, ”<numeric limits>”qui donne des informations sur le type numérique ”T”.
Soit un type ”T”, ”std :: numeric limit<T>”définie des méthodes statiques (accessible sans instance) permettant d’accéder à un ensemble de fonctions renseignant ”T”:
1std : : numericlimit <T>::max() ; // fonction statique retournant l e maximum p o s s i b l e pour l e type T
2std : : numericlimit <T>:: min () ; // l e minimum p o s s i b l e pour l e type T
3 std : : numeric limit <T>:: i s i n t e g e r () ; // booléen indiquant que T est bien un type e n t i e r
4std : : numericlimit <T>:: e p s i l o n () ; // valeur indiquant la l i m i t e numérique d ’ i n d i s t i n c t i o n entre deux T s u c c e s s i f s
Exemple d’utilisation
Initialisation correcte de l’algorithme en cas de liste vide :
Utilisation de <algorithm>
La partie <algorithm> de la STL propose une suite d’algorithmes fréquemment utilisées : tri, partition, ordre lexicographique, etc.
Itérateurs conception (Design
Singletons patterns)
Définition
Les patrons de conception (en anglais Design Patterns) sont des propositions de conception informatiques largement éprouvées dans la résolution de problèmes génie logiciel types.
De nombreux patrons sont définis (cf. Wikipedia), nous en verrons quelques uns
La STL utilise abondamment la notion d’itérateur qui est un patron de conception
Le C++ se prête bien à l’utilisation de patrons
Améliore grandement le travail collaboratif (non intrusion de notions complémentaires, séparation fonctionnelle )
Mais les patrons de conception restent des propositions (pouvant vous donner des idées) et suivre ces modèles n’est pas une fin en soi.
Itérateurs
1 test de fin d’itération : pour un itérateur en C++, un couple d’itérateur est nécessaire : le premier itère, le second marque la fin. Un test d’égalité permet alors de déterminer la fin de l’itération
2 incrémentation ou décrémentation : avance ou recule l’itérateur d’un (ou plusieurs) élément(s)
le test de fin est implémenté en surchargeant l’”operator!=”de la classe de l’itérateur
l’incrémentation est implémentée en surchargeant l’”operator++”de la classe de l’itérateur
Il existe deux versions de l’”operator++”: le préfixé (”++it”) et le post-fixé (”it++”). Le premier ne prend pas d’argument et retourne une référence sur lui-même (”return ?this”). Le second prend un argument entier (non utilisé) et retourne une copie de lui-même avant incrémentation.
Singletons
Utile dans certains contextes : par exemple une classe de synchronisation de ressources, une classe de mappage d’évènements, etc.
Ecrire la classe ”ExempleSingleton”(utilisation minimale du singleton).
159 / 201
Contenu
10 Librairies externes
11 Extension C++ avec Boost
12 Optimisation
Compilation et édition de liens
Librairies externes
Visibilité du code & interfaces
Interfa¸cage avec le ”C”
Il faut pour cela que les types C soit correctement renseignés (au compilateur C++) comme étant C.
Lorsque l’on compile une unité de code en C++ :
1 la macro cplusplus est toujours définie (tout compilateur confondu)
2 la directive extern ”C”permet alors de renseigner le type (structure, fonction)
Librairies externes
Interfa¸cage avec le C : exemple
Remarques sur les inclusions
”private/protected/public”ne sont que des directives destinées au compilateur, et indiquant approximativement l’usage des membres d’une classe.
Pour qu’un utilisateur externe puisse utiliser une classe, il faut qu’il manipule tous les types des méthodes que cette classe appelle, ainsi que tous les types utilisés pour l’implémentation.
Rappel
Les interfaces permettent d’arriver à un résultat correct pour les deux parties, pour un surcouˆt négligeable.
Elles laissent le soin au fournisseur de service de modifier l’implémentation, tant que l’interface du service reste la même
Elles empêchent la divulgation de détails d’implémentation
Elles évitent au client le fardeau de la gestion de type liés à l’implémentation du fournisseur
Elles sont compatibles avec les templates et potentiellement la présence de beaucoup de types
Elles restent souples à manipuler pour le client
Présentation Modes d’utilisation & compilation
Extension C++
Tests unitaires
Thread
Présentation
Boost est un Consortium de développeur C++, travaillant sur des extensions standards du C++.
Il faut utiliser Boost.
Utiliser Boost est une bonne chose, car Boost vous veut du bien.
Il faut convaincre tout le monde d’utiliser Boost.
Boost est le prochain standard.
Modes d’utilisation
Deux manière d’utiliser Boost :
version ”headers”: à inclure dans l’unité de compilation concernée, et c’est tout (ex. : graph, mpl, proto, fusion, asio ) version compilée : certaines (peu) parties de Boost nécessitent une compilation, et (donc) une liaison avec le programme client (ex. : filesystem, regex, mpi, )
Il faut utiliser Boost, c’est simple à installer.
Compilation et installation
Unises (Unix, Linux, Mac, Cygwin, )
$ cd path/to/boost_1_37_0
$ ./configure --prefix=path/to/installation/prefix $ make install
Windows avec MS Visual Studio
Démarrer la ligne de commande Visual (normalement dans le menu démarrer et dans le répertoire de Visual)
$ cd path/to/boost_1_37_0/tools/jam
$
$ cd path/to/boost_1_37_0 $ copy path/to/boost_1_37_0/tools/jam/src/bin.Quelquechose/bjam .
$ bjam --build-dir=D:\temporary_dir --toolset=msvc --buildtype=complete stage
<boost/filesystem>
Ex. : nom des fichiers, gestion de l’arborescence, gestion des droits d’accès, existence, etc.
<boost/test>
Les tests ne démarreront qu’au retour de cette fonction de de déclaration.
<boost/test> : exemple
: exemple d’appel
Questions générales
Optimisations logicielles
”Premature optimization is the root of all evil”(Donald Knuth)
On optimise toujours en fonction d’une ou plusieurs contraintes Ex. : temps de calcul, occupation mémoire, algorithmique
Les contraintes sont parfois contradictoires et/ou conflictuelles.
Couˆt théorique de l’algorithme exprimé par rapport à la taille N des données traitées et exprimé en O(†(N)), pour N ? N?.
† : 1 (temps constant), identité, polynôme, log
Questions classiques :
Réponse mitigée. D’expérience : vrai pour des parties isolées de code, faux pour des projets d’envergure importante. Même débat entre le C et l’assembleur par exemple
Ou` et quand optimiser?
Une fois qu’on a enfin un truc qui fonctionne, selon un design relativement correct.
Remarque : les contraintes influencent nécessairement la phase de design, donc inutile de faire de l’optimisation logicielle trop tôt.
Comment optimiser?
Encore une fois, selon la contrainte.
Nous allons voir quelques éléments d’optimisation algorithmique et logicielle
Choix des conteneurs
Les couˆts d’accès des conteneurs standards sont également standards! Donc garantie multi plate-forme.
Choix des conteneurs (1)
Choix des conteneurs (2)
I Choix du compilateur!!
I Options d’optimisation de plusieurs niveaux (GCC : O1, O2, 03, O45 active un ensemble d’optimisations)
I Choix du processeur cible
I Utilisation automatique de librairies externes (open MP, etc)
I Profondeur des inlines
I Utilisation des fonctions intrinsèques I Optimisations globales à l’édition de lien I
Avantage important : méthode d’optimisation non-intrusive
191 / 201
Comprendre et gérer la bidouille de compilateurs Code rapide (suite) :
Utilisation des inlines
Méthode d’optimisation intrusive!
Pas vraiment une optimisation : le choix de l’inlining reste le choix du compilateur
Utilisation de portions de code assembleur
Très performant (souvent, mais pas toujours)
Très spécifique
Dur
Très dur même
Difficile à maintenir
Plate-forme dépendant
192 / 201
Formation C++ avancée
Synthèse de la formation
Synthèse de la formation
Contenu
Un outil à maˆ?triser
Savoir-faire
La généricité“light”
Release early, release often
. ie. répondant à des spécifications fonctionnelles strictes