| ||
auteurs : LFE, Luc Hermitte, Aurélien Regat-Barrel | ||
Le C++ possède une bibliothèque standard (on parle aussi de SL pour Standard Library) qui est composée, entre autre, d'une bibliothèque de flux, de la bibliothèque standard du C, de la gestion des exceptions, ..., et de la STL (Standard Template Library : bibliothèque de modèles standard). En fait, STL est une appellation historique communément acceptée et
comprise. Dans la norme on ne parle que de SL. Comme son nom l'indique cette bibliothèque utilise fortement les template C++ et le concept de généricité. Elle définit ainsi de nombreux modèles de classes et de fonctions génériques parmi lesquelles les conteneurs (tableau, liste, pile, ensemble, ...) et les algorithmes (copie, tri, recherche, ...) les plus courants. Une particularité importante est son approche orientée itérateurs, ce qui permet par exemple d'utiliser ses algorithmes sur d'autres conteneurs que ceux qu'elle fournit. L'un des avantages de la STL par rapport aux autres bibliothèques remplissant (plus ou moins) le même rôle est qu'elle est standard, et donc théoriquement portable (cependant les limites de certains compilateurs compliquent la chose). Un autre avantage important est son polymorphisme paramétrique qui assure un typage fort sans exigence syntaxique à l'utilisation, c'est à dire par exemple que l'on peut très simplement créer un tableau de ce que l'on veut sans devoir downcaster depuis un horrible type commun tel que void *. Utiliser la STL permet donc d'augmenter de manière significative sa productivité en C++ en écrivant du code fiable, performant et portable. |
| ||
auteurs : LFE, Aurélien Regat-Barrel, Luc Hermitte | ||
Un conteneur (container) est, comme son nom l'indique, un objet qui contient d'autres objets.
Il fournit un moyen de gérer les objets contenus (au minimum ajout / suppression, parfois insertion, tri, recherche, ...) ainsi qu'un accès à ces objets qui dans le cas de la STL consiste très souvent en un itérateur. Les itérateurs permettent de parcourir une collection d'objets sans avoir à se préoccuper de la manière dont ils sont stockés. Ceci permet aussi d'avoir une interface de manipulation commune, et c'est ainsi que la STL fournit des algorithmes génériques qui s'appliquent à la majorité de ses conteneurs (tri, recherche, ...). Parmi les conteneurs disponibles dans la STL on trouve les tableaux (vector), les listes (list), les ensembles (set), les piles (stack), et beaucoup d'autres. |
| ||
auteur : LFE | ||
Les itérateurs (iterator) sont une généralisation des pointeurs : ce sont des objets qui pointent sur d'autres objets. Comme son nom l'indique,
les itérateurs sont utilisés pour parcourir une série d'objets de telle façon que si on incrémente l'itérateur, il désignera l'objet
suivant de la série.
|
| ||
auteur : Aurélien Regat-Barrel | ||
|
| ||
auteur : LFE | ||
La réponse dépend de la nature de ce qui est stocké dans un vecteur. S'il s'agit d'un objet, il n'est pas utile de le détruire, il le sera lorsqu'il est retiré du vecteur, ou lorsque le vecteur est détruit. Par contre, s'il s'agit d'un pointeur sur un objet, il faut le détruire car un pointeur n'est pas un objet. Si cette destruction n'est pas faite, le programme présentera une fuite de mémoire. |
| ||
auteur : Laurent Gomila | ||
La panoplie de conteneurs proposée par la STL est conséquente : conteneurs ordonnés, associatifs, listes chaînées, ... Le choix de la bonne structure dépend principalement des opérations que l'on va effectuer sur nos données : suppression, ajout, recherche, ... Voici un schéma récapitulatif qui vous aidera à y voir plus clair :
Pour stocker des chaînes de caractères, std::string sera le conteneur le plus approprié. Il fournit des fonctions de recherche, de
découpage, et des opérateurs surchargés pour une manipulation plus intuitive (voir Y a-t-il un type chaîne de caractères en C++ ?).
std::string n'étant qu'un typedef sur std::basic_string<char>, pour manipuler d'autres types de caractères (unicode par exemple, ou ne
tenant pas compte de la casse) il suffit d'utiliser le type approprié (voir Comment manipuler des chaînes de caractères Unicode ? et Comment manipuler des chaînes de caractères ne tenant pas compte de la casse ?).
A noter qu'il existe également une classe pour manipuler les champs de bits : std::bitset.
On peut aussi trouver dans certaines versions de la STL d'autres conteneurs, non standards mais bien connus : tables de hachage, listes
simplements chaînées, etc. Leur utilisation est en général similaire à celle des autres conteneurs standards, mais pensez tout de même à
bien vous documenter avant de les utiliser !
|
| ||||
auteur : Laurent Gomila | ||||
Effacer des éléments d'un conteneur est une tâche fréquente, mais souvent mal réalisée.
L'approche la plus naïve est de parcourir le conteneur à l'aide d'une boucle, et de supprimer (via la fonction membre erase) les éléments
concernés. Mais attention, il faut veiller à bien manipuler l'itérateur afin de ne pas oublier d'éléments ou de pointer en dehors du
conteneur. Le méthode correcte est la suivante :
A noter que cette méthode ne fonctionne pas avec les conteneurs associatifs (set, map, multiset, multimap) : leur
fonction erase ne renvoie rien.
Il existe cependant des fonctions plus appropriées pour chaque conteneur. Pour vector et string, on utilise ce qu'on appelle l'idiome erase-remove : on appelle std::remove (ou std::remove_if qui va prendre en paramètre un prédicat plutôt qu'une valeur -- voir Qu'est-ce qu'un prédicat ?) qui va déplacer les éléments à supprimer à la fin du conteneur, puis la fonction membre erase qui va les supprimer définitivement.
Pour list, on utilise plus simplement la fonction membre remove (ou remove_if) qui contrairement à std::remove va bien supprimer les
éléments.
Pour les [multi]set et les [multi]map il existe la fonction membre erase, qui remplit exactement le même rôle que list::remove (attention aux
noms, cela peut facilement porter à confusion). A noter que pour les [multi]map, erase prend en paramètre la clé à effacer, non la
valeur.
|
| ||
auteur : Laurent Gomila | ||
L'utilisation de code C dans un projet C++ est parfois inévitable, si l'on doit manipuler des bibliothèques écrites en C ou encore des
fonctions codées dans un mauvais style. Il convient dans ce cas de prendre quelques précautions si l'on manipule des tableaux.
- Utilisez std::vector si vous devez passer un tableau à une fonction C, car seul ce conteneur garantit la contiguïté des données en
mémoire.
- Si vous devez alimenter un conteneur avec les données d'un tableau C, la STL fournit tout ce qu'il faut pour le faire proprement.
- Pour les chaînes de caractères, std::string permet d'obtenir une chaîne C (caractères contigüs et '\0' final) via sa fonction membre
c_str(). Attention cependant : la chaîne renvoyée peut très bien être temporaire et ne pas survivre à l'appel, il ne faut donc jamais
stocker le pointeur retourné pour l'utiliser ultérieurement. Voir Comment convertir un char* en un string ? et Comment convertir une string en char* ?. |
| ||
auteur : Laurent Gomila | ||
Toute classe surchargeant l'opérateur d'appel de fonction operator() est qualifiée de classe foncteur (functor class). Les objets instanciés d'une telle classe sont appelés objets fonctionnels (function objects) ou foncteurs (functors). La STL utilise beaucoup ce concept pour personnaliser le comportement de ses classes/fonctions. Notez qu'on peut tout à fait utiliser des pointeurs sur fonction au lieu de foncteurs (il suffit de pouvoir appliquer l'opérateur () à l'objet passé en paramètre), mais on préfèrera les foncteurs car ils offrent plus de performances et de flexibilité. En effet operator() peut être entièrement inliné, et on peut passer des paramètres au foncteur via son constructeur pour plus de souplesse. Voici un exemple typique de foncteurs tiré de la question [Exemple] Comment trier une séquence selon un critère personnalisé ?:
Les foncteurs sont en particulier utilisés dans le cadre des prédicats (lire Qu'est-ce qu'un prédicat ?).
|
| ||
auteur : Aurélien Regat-Barrel | ||
Les prédicats (predicate) sont un type particulier de foncteurs qui renvoient un booléen ou quelque chose de convertible en booléen ce qui les rend utilisables dans des expressions logiques. Il existe un certain nombre de prédicats prédéfinis dans la STL, en particulier en ce qui concerne les opérateurs logiques et arithmétiques élémentaires :
L'intérêt des prédicats est qu'ils peuvent être utilisés comme critère d'évaluation dans bon nombre d'algorithmes et conteneurs de la STL.
Par exemple le conteneur std::set utilise par défaut le prédicat std::less afin d'organiser ses éléments de manière croissante. Il est facile de changer ce critère pour créer un std::set organisé de manière décroissante en spécifiant std::greater.
Mais les prédicats révèlent toute leur puissance lorsqu'ils sont utilisés conjointement avec les foncteurs réducteurs std::bind1st et std::bind2nd. Pour plus d'informations lire A quoi servent les fonctions bind1st et bind2nd ?.
|
| ||||
auteur : Aurélien Regat-Barrel | ||||
std::bind1st et std::bind2nd sont un type particulier de foncteurs appelés foncteurs réducteurs.
Ils permettent de facilement créer un prédicat unaire (c.a.d acceptant un seul paramètre) à partir d'un prédicat binaire (en acceptant 2 paramètres).
Ceci est fait en figeant la valeur d'un des deux paramètres du prédicat binaire (c.a.d à le remplacer par une valeur fixe). Soit l'exemple suivant où le prédicat plus_grand_que_10 est créé afin de compter grâce à std::count_if le nombre de valeurs supérieures à 10 dans un conteneur:
plus_grand_que_10 est un prédicat unaire obtenu en figeant le second argument de l'opérateur de comparaison supérieur à la valeur 10.
On aurait pu aussi obtenir le même résultat en figeant le premier paramètre de l'opérateur inférieur cette fois-ci:
bind1st et bind2nd permettent de simplifier cette tâche fastidieuse de définition d'un prédicat binaire. bind1st permet de figer le premier argument d'un prédicat binaire. bind2nd permet de figer le second argument d'un prédicat binaire. Ils s'utilisent de cette manière:
L'exemple précédent peut alors être simplifié ainsi:
|
| ||
auteur : Laurent Gomila | ||
|
| ||
auteur : Laurent Gomila | ||
Voir aussi la question [Exemple] Comment détruire les pointeurs d'un conteneur en utilisant Boost ?.
|
| ||
auteur : Laurent Gomila | ||
La fonction std::sort de la bibliothèque standard n'est compatible
qu'avec les itérateurs à accès aléatoire (ceux pour lesquels les
opérateurs + et - sont définis), c'est-à-dire les itérateurs de
std::vector, std::string, std::deque et les pointeurs bruts. En effet,
l'accès aléatoire est l'un des prérequis de l'algorithme de tri.
Pour trier un conteneur de type std::list, il faut utiliser la fonction membre std::list::sort :
Notez que cette version particulière sera plus lente que la version générique.
Pour les utilisateurs de Visual C++ 6, la STL de ce dernier ne définit
pas la version prenant en paramètre un foncteur (à cause du manque de
support des fonctions membres templates). Dans ce cas, une solution
peut être de passer par un vecteur :
|
| ||
auteur : r0d | ||
Tout cela est expliqué dans le tutoriel de r0d : http://r0d.developpez.com/articles/algos-stl/
|
Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.