En utilisant uniquement le langage de base, chaque tâche est fastidieuse (dans n’importe quel langage). En utilisant une bibliothèque adaptée, chaque tâche peut devenir raisonnablement simple.
La bibliothèque standard a connu une croissance constante au fil des années. Sa description dans le standard est désormais plus grande que celle des caractéristiques du langage. Il est donc probable que cette section des directives sur la bibliothèque finira par atteindre, voire dépasser, la taille des autres sections.
« Il faut un niveau supplémentaire de numérotation des règles »
Résumé des composantes de la bibliothèque standard C++:
Résumé des règles de la bibliothèque standard:
stdGagner du temps. Ne pas réinventer la roue.
Ne pas reproduire le travail des autres.
Profiter du travail des autres lorsqu’ils apportent des améliorations.
Aider les autres lorsque vous apportez des améliorations.
Plus de personnes connaissent la bibliothèque standard.
Elle est plus susceptible d’être stable, bien maintenue et largement disponible que votre propre code ou la plupart des autres bibliothèques.
stdAjouter à std peut changer la signification d’un code qui est autrement conforme au standard.
Les ajouts à std peuvent entrer en conflit avec les futures versions du standard.
namespace std { // BAD: violates standard
class My_vector {
// . . .
};
}
namespace Foo { // GOOD: user namespace is allowed
class My_vector {
// . . .
};
}
Possible, mais chaotique et susceptible de causer des problèmes sur les plateformes.
Parce qu’en effet, violer cette règle peut conduire à un comportement indéfini, à des corruptions mémoire et à toutes sortes d’autres erreurs graves.
Il s’agit d’une méta-règle semi-philosophique, nécessitant de nombreuses règles concrètes de soutien. Nous l’avons besoin comme un parapluie pour les règles plus spécifiques.
Résumé des règles plus spécifiques:
???
Résumé des règles sur les conteneurs:
array ou vector devant un tableau Cvector par défaut sauf si vous avez une raison d’utiliser un autre conteneurmemset ou memcpy pour des arguments qui ne sont pas trivially-copyablearray ou vector plutôt qu’un tableau CLes tableaux C sont moins sûrs, et n’ont aucune amélioration par rapport à array et vector.
Pour un tableau de longueur fixe, utilisez std::array, qui ne se dégrade pas en pointeur lorsqu’il est passé à une fonction et connaît sa taille.
De plus, comme un tableau natif, un std::array alloué sur la pile conserve ses éléments sur la pile.
Pour un tableau de longueur variable, utilisez std::vector, qui peut également changer sa taille et gérer l’allocation mémoire.
int v[SIZE]; // BAD
std::array<int, SIZE> w; // ok
int* v = new int[initial_size]; // BAD, owning raw pointer
delete[] v; // BAD, manual delete
std::vector<int> w(initial_size); // ok
Utilisez gsl::span pour les références non propriétaires vers un conteneur.
Comparer la performance d’un tableau fixe alloué sur la pile à un vector avec ses éléments sur le tas est futile.
Vous pourriez tout aussi bien comparer un std::array sur la pile à la résultante d’un malloc() accédé via un pointeur.
Pour la plupart du code, même la différence entre l’allocation pile et l’allocation au tas n’a pas vraiment d’importance, mais la commodité et la sécurité du vector le rendent plus judicieux.
Les personnes travaillant avec du code où cette différence a de l’importance sont tout à fait capables de choisir entre array et vector.
std::array.vector par défaut sauf si vous avez une raison d’utiliser un autre conteneurvector et array sont les seuls conteneurs standards qui offrent les avantages suivants :
Habituellement, vous avez besoin d’ajouter et de supprimer des éléments du conteneur, alors utilisez vector par défaut ; si vous n’avez pas besoin de modifier la taille du conteneur, utilisez array.
Même si d’autres conteneurs semblent mieux adaptés, comme map pour une performance O(log N) ou une list pour une insertion efficiente au milieu, un vector fonctionnera généralement mieux pour les conteneurs d’une taille de quelques KB.
string ne doit pas être utilisé comme un conteneur de caractères individuels.
string est une chaîne de texte ; si vous voulez un conteneur de caractères, utilisez vector</*char_type*/> ou array</*char_type*/> à la place.
Si vous avez une bonne raison d’utiliser un autre conteneur, utilisez-le à la place.
Par exemple :
vector convient à vos besoins mais que vous n’avez pas besoin que le conteneur soit de taille variable, utilisez array à la place.vector trié soit irréalisable, alors utilisez un unordered_map ou map à la place.Pour initialiser un vecteur avec un nombre d’éléments, utilisez l’initialisation ().
Pour initialiser un vecteur avec une liste d’éléments, utilisez l’initialisation {}.
vector<int> v1(20); // v1 a 20 éléments avec la valeur 0 (vector<int>{})
vector<int> v2 {20}; // v2 a 1 élément avec la valeur 20
Préférer la syntaxe {}-initialiser @TODO-LINK
vector dont la taille ne change jamais après construction (tels que parce qu’il est const ou parce qu’aucune fonction non-const n’est appelée sur celui-ci).array à la place.Lire ou écrire au-delà d’une plage d’éléments alloués conduit généralement à des erreurs graves, à de mauvais résultats, à des plantages et à des violations de sécurité.
Les fonctions de la bibliothèque standard qui s’appliquent aux intervalles d’éléments possèdent toutes (ou pourraient posséder) des overloads sécurisés par les bornes qui prennent span.
Les types standards tels que vector peuvent être modifiés pour exécuter des vérifications de bornes sous le profil de bornes (de façon compatible, par ex. en ajoutant des contrats), ou utilisés avec at().
Idéalement, la garantie in-bounds devrait être statiquement imposée.
Par exemple :
range-for ne peut pas dépasser la plage du conteneur auquel elle est appliquéev.begin(),v.end() est facilement déterminé comme sécurisé par les bornesCes boucles sont aussi rapides que n’importe quel équivalent non vérifié/non sûr.
Souvent, un simple contrôle prédéfini peut éliminer le besoin de vérifier les indices individuels.
Par exemple :
v.begin(),v.begin()+i l’expression i peut être facilement vérifiée par rapport à v.size()De telles boucles peuvent être beaucoup plus rapides que d’accès à chaque élément vérifié individuellement.
void f()
{
array<int, 10> a, b;
memset(a.data(), 0, 10); // BAD, and contains a length error (length = 10 * sizeof(int))
memcmp(a.data(), b.data(), 10); // BAD, and contains a length error (length = 10 * sizeof(int))
}
De plus, std::array<>::fill() ou std::fill() ou même un initialiseur vide sont de meilleurs candidats que memset().
void f()
{
array<int, 10> a, b, c{}; // c est initialisé à zéro
a.fill(0);
fill(b.begin(), b.end(), 0); // std::fill()
fill(b, 0); // std::ranges::fill()
if ( a == b ) {
// ...
}
}
Si le code utilise une bibliothèque standard non modifiée, il existe encore des solutions de contournement qui permettent d’utiliser std::array et std::vector dans une manière sécurisée par les bornes. Le code peut appeler la méthode membre .at() sur chaque classe, ce qui lèvera une exception std::out_of_range. Alternativement, le code peut appeler la fonction libre at(), ce qui imposera une vérification rapide (ou une action personnalisée) sur une violation de bornes.
void f(std::vector<int>& v, std::array<int, 12> a, int i)
{
v[0] = a[0]; // BAD
v.at(0) = a[0]; // OK (alternative 1)
at(v, 0) = a[0]; // OK (alternative 2)
v.at(0) = a[i]; // BAD
v.at(0) = a.at(i); // OK (alternative 1)
v.at(0) = at(a, i); // OK (alternative 2)
}
Cette règle fait partie du profil de bornes @TODO-LINK
memset ou memcpy pour des arguments qui ne sont pas trivially-copyableFaire cela perturbe la sémantique des objets (par ex. en écrasant un vptr).
De même pour (w)memset, (w)memcpy, (w)memmove, et (w)memcmp
struct base {
virtual void update() = 0;
};
struct derived : public base {
void update() override {}
};
void f(derived& a, derived& b) // goodbye v-tables
{
memset(&a, 0, sizeof(derived));
memcpy(&a, &b, sizeof(derived));
memcmp(&a, &b, sizeof(derived));
}
À la place, définissez des fonctions d’initialisation, de copie et de comparaison appropriées
void g(derived& a, derived& b)
{
a = {}; // default initialize
b = a; // copy
if (a == b) do_something(a, b);
}
TODO Notes:
memcmp et les emballer dans le GSL.vector qui ne sont pas entièrement vérifiés, le but est que ces fonctionnalités soient vérifiées lorsque le profil de bornes est activé, et non vérifiées lorsqu’elles sont appelées depuis le code hérité, éventuellement en utilisant ce qui est proposé en même temps par plusieurs membres WG21.La manipulation du texte est un sujet vaste. std::string ne couvre pas tout. Cette section essaie principalement de clarifier la relation de std::string avec char*, zstring, string_view et gsl::span<char>. La question importante des jeux de caractères non ASCII et des encodages (par ex. wchar_t, Unicode et UTF‑8) sera abordée ailleurs.
Voir aussi : Expressions régulières
Ici, nous appelons « séquence de caractères » ou « chaîne » pour désigner une séquence de caractères destinées à être lues comme du texte (d’une manière ou d’une autre, éventuellement). Nous ne considérons pas ???
Résumé des chaînes :
std::string pour détenir des séquences de caractèresstd::string_view ou gsl::span<char> pour référencer des séquences de caractèreszstring ou czstring pour référencer une séquence C‑style nul terminée de caractèreschar* pour référencer un caractère uniquestd::string lorsque vous avez besoin d’opérations sensibles à la localegsl::span<char> plutôt que std::string_view lorsque vous avez besoin de muter une chaînes pour les littéraux de chaînes destinés à être standard-library stringsVoir aussi :
std::string pour détenir des séquences de caractèresstring gère correctement l’allocation, la propriété, la copie, l’expansion graduelle et propose une variété d’opérations utiles.
vector<string> read_until(const string& terminator)
{
vector<string> res;
for (string s; cin >> s && s != terminator; ) // read a word
res.push_back(s);
return res;
}
Notez comment >> et != sont fournis pour string (en tant qu’exemples d’opérations utiles) et qu’il n’y a aucune allocation explicite, désallocation ou vérification de plage (string s’en occupe).
En C++17, nous pourrions utiliser string_view comme argument, plutôt que const string& pour permettre plus de flexibilité aux appelants :
vector<string> read_until(string_view terminator) // C++17
{
vector<string> res;
for (string s; cin >> s && s != terminator; ) // read a word
res.push_back(s);
return res;
}
Ne pas utiliser de chaînes C‑style pour les opérations qui nécessitent une gestion mémoire non triviale
char* cat(const char* s1, const char* s2) // beware!
// return s1 + '.' + s2
{
int l1 = strlen(s1);
int l2 = strlen(s2);
char* p = (char*) malloc(l1 + l2 + 2);
strcpy(p, s1, l1);
p[l1] = '.';
strcpy(p + l1 + 1, s2, l2);
p[l1 + l2 + 1] = 0;
return p;
}
Nous avons‑t‑on le bon ? … … ???
Ne pas supposer que string est plus lent qu’une technique de bas niveau sans mesurer et souvenez‑vous que tout n’est pas du code critique de performance.
Ne pas optimiser prématurément @TODO-LINK
???
std::string_view ou gsl::span<char> pour référencer des séquences de caractèresstd::string_view ou gsl::span<char> fournit un accès simple et (potentiellement) sûr aux séquences de caractères indépendamment de la façon dont ces séquences sont allouées et stockées.
vector<string> read_until(string_view terminator);
void user(zstring p, const string& s, string_view ss)
{
auto v1 = read_until(p);
auto v2 = read_until(s);
auto v3 = read_until(ss);
// ...
}
std::string_view (C++17) est en lecture seule.
???
zstring ou czstring pour référencer une séquence C‑style nul terminée de caractèresLisibilité.
Déclaration d’intention.
Une simple char* peut être un pointeur vers un caractère unique, un pointeur vers un tableau de caractères, un pointeur vers une chaîne C‑style (nul terminée) ou même vers un petit entier.
Distinction entre ces alternatives empêche les malentendus et les bugs.
void f1(const char* s); // s est probablement une chaîne
Tout ce que nous savons, c’est qu’elle doit être le pointeur nul ou pointer vers au moins un caractère
void f1(zstring s); // s est une chaîne C‑style ou le pointeur nul
void f1(czstring s); // s est une chaîne C‑style constante ou le pointeur nul
void f1(std::byte* s); // s est un pointeur vers un byte (C++17)
Ne pas convertir une chaîne C‑style en string sauf s’il y a une raison.
Comme tout autre “plain pointer”, un zstring ne doit pas représenter la propriété.
Il existe des milliards de lignes de C++ « out there », l’utilisation de char* et const char* sans documenter l’intention. Ils sont utilisés dans une large gamme de façons. … (Compléter la traduction).
[] sur un char*delete sur un char*free() sur un char*char* pour référencer un caractère uniqueLa variété d’utilisations de char* dans le code actuel est une source majeure d’erreurs.
char arr[] = {'a', 'b', 'c'};
void print(const char* p)
{
cout << p << '\n';
}
void use()
{
print(arr); // run-time error; potentially very bad
}
Le tableau arr n’est pas une chaîne C‑style car il n’est pas nul‑terminé.
Voir zstring, string, et string_view.
[] sur un char*std::byte pour référencer des valeurs bytes qui ne représentent pas nécessairement des caractèresL’utilisation de char* pour représenter un pointeur vers quelque chose qui n’est pas nécessairement un caractère provoque une confusion et désactive d’importants optimisations.
???
C++17
???
std::string lorsque vous avez besoin d’opérations sensibles à la localestd::string supporte les facilities de locale du standard.
???
???
???
gsl::span<char> plutôt que std::string_view lorsque vous avez besoin de muter une chaînestd::string_view est en lecture seule.
???
???
Le compilateur signalera les tentatives d’écrire dans un string_view.
s pour les littéraux de chaînes destinés à être standard-library stringsDéclaration directe de l’idée minimise les erreurs.
auto pp1 = make_pair("Tokyo", 9.00); // {C-style string,double} intended?
pair<string, double> pp2 = {"Tokyo", 9.00}; // a bit verbose
auto pp3 = make_pair("Tokyo"s, 9.00); // {std::string,double} // C++14
pair pp4 = {"Tokyo"s, 9.00}; // {std::string,double} // C++17
???
iostreams est une bibliothèque d’E/S typée, extensible, formatée et non formatée pour le streaming d’E/S.
Elle supporte plusieurs stratégies de mise en tampon (et extensibles par l’utilisateur) ainsi que plusieurs locales.
Elle peut être utilisée pour l’E/S conventionnelle, la lecture et l’écriture en mémoire (flux de chaînes), et pour des extensions définies par l’utilisateur, comme le streaming sur réseau (asio : pas encore standardisé).
Résumé des règles pour iostream :
printf, appelez ios_base::sync_with_stdio(false)endlGagner du temps. Ne pas réinventer la roue. Ne pas reproduire le travail des autres.
char c;
char buf[128];
int i = 0;
while (cin.get(c) && !isspace(c) && i < 128)
buf[i++] = c;
if (i == 128) {
// ... handle too long string ....
}
Meilleure (beaucoup plus simple et probablement plus rapide) :
string s;
s.reserve(128);
cin >> s;
et le reserve(128) est probablement inutile.
????
Les erreurs sont généralement mieux gérées dès que possible.
Si l’entrée n’est pas validée, chaque fonction doit être écrite pour gérer les données bad (et ce n’est pas pratique).
???
???
Les iostream sont sûrs, flexibles et extensibles.
// write a complex number:
complex<double> z{ 3, 4 };
cout << z << '\n';
complex est un type défini par l’utilisateur et son I/O est défini sans modifier la bibliothèque iostream.
// read a file of complex numbers:
for (complex<double> z; cin >> z; )
v.push_back(z);
??? performance ???
printfIl est souvent (et souvent correctement) dit que la famille printf a deux avantages par rapport aux iostreams :
Si vous avez besoin d’un bonio performance, vous pouvez presque toujours faire mieux que printf.
gets(), scanf() avec %s, et printf() avec %s sont des risques de sécurité (vulnérables à un overflow de buffer et généralement mauvais).
C11 définit des extensions obligatoires qui vérifient leurs arguments. Si présent, gets_s(), scanf_s(), et printf_s() sont plus sûrs, mais ils ne sont pas toujours disponibles.
Optionnellement flag <cstdio> et <stdio.h>.
printf, appelez ios_base::sync_with_stdio(false)La synchronisation d’un iostream avec l’I/O style printf peut coûter cher.
cin et cout sont par défaut synchronisés avec la fonction printf.
int main()
{
ios_base::sync_with_stdio(false);
// ... use iostreams ...
}
???
endlLe manipueur endl est en grande partie équivalent à '\n' et "\n" ; comme la façon la plus courante, il ralentit la sortie simplement par le flush supplémentaire.
Cette réduction peut être importante par rapport à l’output de style printf.
cout << "Hello, World!" << endl; // deux opérations de sortie et un flush
cout << "Hello, World!\n"; // un seul pas de ^
Pour ce qui est de l’interaction cin / cout (et équivalents) il n’y a pas de raison de forcer un flush ; c’est fait automatiquement.
Pour écrire dans un fichier, il n’y a rarement besoin de flush.
Pour les flux de chaînes (ostringstream par exemple), l’insertion d’un endl est totalement équivalente à l’insertion d’un '\n' caractère, mais aussi dans ce cas, endl peut être significativement plus lent.
endl ne prend pas en charge la production d’une fin de ligne spécifique au système d’exploitation (comme "\r\n" sous Windows).
Donc pour un flux de chaînes, s << endl insère simplement un seul caractère, '\n'.
Outre l’aspect (souvent important) de la performance, le choix entre '\n' et endl est presque entièrement esthétique.
<regex> est la bibliothèque d’expressions régulières du standard C++.
Elle prend en charge une variété de conventions de modèles d’expressions régulières.
Pour les travaux à haute performance, envisager une bibliothèque d’expressions régulières tierce.
<chrono> (défini dans le namespace std::chrono) fournit les notions de time_point et duration ainsi que les fonctions pour afficher le temps dans différentes unités.
Il fournit des horloges pour enregistrer des time_point.
???
Résumé des règles de la bibliothèque C :
Un longjmp ignore les destructeurs, invalidant ainsi toutes les stratégies de gestion de ressources reposant sur RAII.
Marquer toutes les occurrences de longjmp et setjmp.