
|
auteurs :
LFE, Luc Hermitte, Aurelien.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, Aurelien.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 :
Aurelien.Regat-Barrel | #include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using std::cout;
int main()
{
std::vector<int> v;
v.push_back( 10 );
cout << v.front() << '\n';
cout << v.back() << '\n';
v.pop_back();
if ( v.empty() )
{
cout << "Tout est normal : tableau vide\n";
}
v.resize( 10 );
cout << v.size() << '\n';
v[ 9 ] = 5;
std::fill( v.begin(), v.end(), 100 );
v.clear();
v.reserve( 50 );
cout << v.size() << '\n';
cout << v.capacity() << '\n';
for ( int i = 0; i < 50; ++i )
{
v.push_back( i );
}
cout << v.size() << '\n';
cout << *std::max_element( v.begin(), v.end() ) << '\n';
v.resize( 5 );
std::sort( v.begin(), v.end() );
for ( size_t i = 0, size = v.size(); i < size; ++i )
{
cout << v[ i ] << '\t';
}
cout << '\n';
try
{
v.at( v.size() ) = 10;
}
catch ( const std::out_of_range & )
{
cout << "at() a levé une exception std::out_of_range\n";
}
for ( std::vector<int>::reverse_iterator i = v.rbegin();
i != v.rend();
++i )
{
cout << *i << '\t';
}
cout << '\n';
std::vector<int> v2( 10 );
v2.at( 9 ) = 5;
std::vector<int> v3( 10, 20 );
cout << std::accumulate( v3.begin(), v3.end(), 0 ) << '\n';
v = v3;
if ( std::equal( v.begin(), v.end(), v3.begin() ) )
{
cout << "v est bien une copie conforme de v3\n";
}
} |
|
|
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 :
Loulou24 |
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 :
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 :
Loulou24 |
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 :
#include <set>
std::set<int> s;
for (std::set<int>::iterator it = s.begin(); it != s.end(); )
{
if (*it == 5)
it = s.erase(it);
else
++it;
} |
A noter que cette méthode fonctionne avec tout type de conteneur (vector, string, list, deque, set, map, multiset, multimap).
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
élements à supprimer à la fin du conteneur, puis la fonction membre erase qui va les supprimer définitivement.
#include <string>
#include <vector>
#include <algorithm>
std::string s = "blah blah";
s.erase(std::remove(s.begin(), s.end(), 'h'), s.end());
std::vector v;
v.erase(std::remove_if(v.begin(), v.end(), std::bind2nd(std::greater<int>(), 3)), v.end()); |
Pour list, on utilise plus simplement la fonction membre remove (ou remove_if) qui contrairement à std::remove va bien supprimer les
éléments.
std::list<int> l;
l.remove(5);
l.remove_if(std::bind2nd(std::greater<int>(), 3) |
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.
#include <map>
#include <string>
std::map<std::string, int> m;
m.erase("toto"); |
|
|
auteur :
Loulou24 |
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 contiguité des données en
mémoire.
void Fonction_C(const int* Tableau, unsigned int Taille);
std::vector<int> v;
Fonction_C(&v[0], v.size()); |
- 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.
int* Fonction_C(int* Taille);
int Taille = 0;
int* Tableau = Fonction_C(&Taille);
std::set<int> s(Tableau, Tableau + Taille);
std::list<int> l;
l.assign(Tableau, Tableau + Taille);
std::vector<int> v;
std::copy(Tableau, Tableau + Taille, std::back_inserter(v)); |
- 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 :
Loulou24 |
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é ?:
struct A
{
int Number;
std::string String;
};
struct SortByNumber
{
bool operator ()( const A & a1, const A & a2 ) const
{
return a1.Number < a2.Number;
}
};
struct SortByString
{
bool operator ()( const A & a1, const A & a2 ) const
{
return a1.String < a2.String;
}
}; |
|
|
auteur :
Aurelien.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:
int a = 2;
if ( std::greater<int>()( a, 1 ) )
{
} |
L'intérêt des prédicats est qu'il 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.
std::set< std::greater<int> > entiersTriesParOrdreDecroissant; |
|
|
auteur :
Aurelien.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:
#include <iostream>
#include <vector>
struct plus_grand_que_10
{
bool operator ()( int N ) const
{
return N > 10;
}
};
int main()
{
std::vector<int> v;
v.push_back( 0 );
v.push_back( 5 );
v.push_back( 10 );
v.push_back( 15 );
v.push_back( 20 );
int n = std::count_if(
v.begin(),
v.end(),
plus_grand_que_10() );
std::cout << n << '\n';
} |
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:
struct plus_grand_que_10
{
bool operator ()( int N ) const
{
return 10 < N;
}
}; |
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:
<bind>( <foncteur>, <valeur du paramètre à figer> ); |
L'exemple précédent peut alors être simplifié ainsi:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v;
v.push_back( 0 );
v.push_back( 5 );
v.push_back( 10 );
v.push_back( 15 );
v.push_back( 20 );
int n = std::count_if(
v.begin(),
v.end(),
std::bind2nd( std::greater<int>(), 10 ) );
std::cout << n << '\n';
n = std::count_if(
v.begin(),
v.end(),
std::bind1st( std::less<int>(), 10 ) );
std::cout << n << '\n';
} |
|
|
auteur :
Loulou24 | #include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <set>
struct A
{
A(int i, std::string s) : Number(i), String(s) {}
int Number;
std::string String;
};
std::ostream& operator <<(std::ostream& Stream, const A& a)
{
return Stream << a.Number << " " << a.String;
}
struct SortByNumber
{
bool operator ()(const A& a1, const A& a2) const
{
return a1.Number < a2.Number;
}
};
struct SortByString
{
bool operator ()(const A& a1, const A& a2) const
{
return a1.String < a2.String;
}
};
int main()
{
std::vector<A> v;
v.push_back(A(1, "salut"));
v.push_back(A(5, "hello"));
v.push_back(A(2, "buenos dias"));
std::sort(v.begin(), v.end(), SortByNumber());
std::copy(v.begin(), v.end(), std::ostream_iterator<A>(std::cout, "\n"));
std::cout << std::endl;
std::sort(v.begin(), v.end(), SortByString());
std::copy(v.begin(), v.end(), std::ostream_iterator<A>(std::cout, "\n"));
std::cout << std::endl;
std::set<A, SortByString> s;
s.insert(A(1, "salut"));
s.insert(A(5, "hello"));
s.insert(A(2, "buenos dias"));
std::copy(s.begin(), s.end(), std::ostream_iterator<A>(std::cout, "\n"));
return 0;
} |
|
|
auteur :
Loulou24 | #include <list>
#include <algorithm>
struct Delete
{
template <class T> void operator ()(T*& p) const
{
delete p;
p = NULL;
}
};
int main()
{
std::list<int*> l;
l.push_back(new int(5));
l.push_back(new int(0));
l.push_back(new int(1));
l.push_back(new int(6));
std::for_each(l.begin(), l.end(), Delete());
return 0;
} |
|
Consultez les autres F.A.Q's
 
Ce document issu de http://www.developpez.com est soumis à la licence
GNU FDL traduit en français
ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
Certaines parties de ce document sont sous copyright Marshall Cline
Les codes sources présentés sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance. Pour le reste, ce document constitue une oeuvre intellectuelle protégée par les droits d'auteurs.
Ce document issu de http://www.developpez.com est soumis à trois licences, en fonction des contributeurs :
- Les contributions de Clément Cunin et LFE sont soumises aux termes de la la licence GNU FDL traduite en français ici.
Permission vous est donnée de distribuer, modifier des copies des contributions de Clément Cunin et LFE tant que cette note apparaît clairement :
"Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL
traduite en français ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement".
- Les contributions de Marshall Cline sont sous copyright
- Pour ce qui est des autres contributions : Copyright © 2005 Developpez LLC : Tous droits réservés Developpez LLC.
Aucune reproduction, ne peut en être faite sans l'autorisation expresse de Developpez LLC.
Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
Cette page est déposée à la SACD.
|