La GSL est une petite bibliothèque de fonctionnalités conçues pour soutenir cet ensemble de directives. Sans ces fonctionnalités, les directives devraient être beaucoup plus restrictives sur les détails du langage.
La bibliothèque de support des Core Guidelines est définie dans l’espace de noms gsl et les noms peuvent être des alias de la bibliothèque standard ou d’autres bibliothèques bien connues. L’utilisation de l’indirection (au moment de la compilation) via l’espace de noms gsl permet l’expérimentation ainsi que la création de variantes locales des fonctionnalités de support.
La GSL est une bibliothèque uniquement d’en‑têtes, et se trouve à l’adresse GSL: Guidelines support library. Les fonctionnalités de la bibliothèque de support sont conçues pour être extrêmement légères (sans frais d’exécution) de sorte qu’elles n’imposent aucun surcoût par rapport à l’utilisation d’alternatives classiques. Lorsqu’on le souhaite, elles peuvent être « instrumentées » avec des fonctionnalités supplémentaires (par ex. : des vérifications) pour des tâches telles que le débogage.
Ces Directives utilisent des types de la bibliothèque standard (par ex. : C++17) en plus de ceux provenant de la GSL. Par exemple, nous supposons l’existence d’un type variant, mais celui‑ci n’est pas encore présent dans la GSL. Au final, utilisez celui qui a été intégré à C++17.
Certains des types GSL répertoriés ci‑dessous peuvent ne pas être pris en charge par la bibliothèque que vous utilisez en raison de contraintes techniques, comme les limites des versions actuelles de C++. Par conséquent, veuillez consulter la documentation de votre GSL pour en savoir davantage.
Pour chaque type GSL ci‑dessous, nous indiquons une invariant pour ce type. Cette invariant est valable tant que le code utilisateur ne modifie l’état d’un objet GSL qu’en utilisant les fonctions membres ou libres fournies par le type (c’est‑à‑dire que le code utilisateur ne contourne pas l’interface du type pour changer la valeur/les bits de l’objet en violant une autre règle des Directives).
Résumé des composants GSL :
Nous envisageons une spécification semi‑formelle de style « ISO C++ standard » pour la GSL.
Nous nous appuyons sur la bibliothèque standard ISO C++ et espérons que des parties de la GSL seront incorporées à la bibliothèque standard.
Ces types permettent à l’utilisateur de distinguer les pointeurs propriétaires des pointeurs non‑propriétaires, ainsi que les pointeurs vers un seul objet des pointeurs vers le premier élément d’une séquence.
Ces « vues » ne sont jamais propriétaires.
Les références ne sont jamais propriétaires (voir R.4). @TODO-LINK
Remarque : les références peuvent facilement dépasser la durée de vie des objets auxquels elles font référence (renvoyer une variable locale par référence, conserver une référence à un élément d’un vecteur puis appeler push_back, se lier à std::max(x, y + 1), etc.). Le profil de sécurité de durée de vie vise à traiter ces cas, mais même ainsi, owner<T&> n’a pas de sens et est découragé.
Les noms sont principalement du style de la bibliothèque standard (minuscules et séparateurs de soulignement) :
T* // Le `T*` n’est pas propriétaire, peut être nul ; on suppose qu’il pointe vers un seul élément.
T& // Le `T&` n’est pas propriétaire et ne peut jamais être une « référence nulle » ; les références sont toujours liées à des objets.
La notation « pointeur brut » (p. ex. int*) est supposée avoir son sens le plus courant ; c’est‑à‑dire qu’un pointeur pointe vers un objet, mais n’en est pas propriétaire.
Les propriétaires devraient être convertis en gestionnaires de ressources (p. ex. std::unique_ptr ou std::vector<T>) ou marqués owner<T*>.
owner<T*> // un `T*` qui possède l’objet pointé/référencé ; peut être `nullptr`.
owner est utilisé pour marquer les pointeurs propriétaires dans du code qui ne peut pas être migré vers l’utilisation de véritables gestionnaires de ressources. Les raisons incluent :
Un owner<T> diffère d’un gestionnaire de ressource pour un T en ce qu’il nécessite toujours un delete explicite.
Un owner<T> est supposé référencer un objet sur le tas (heap).
Si quelque chose ne doit pas être nullptr, indiquez‑le :
not_null<T> // `T` est généralement un type pointeur (p. ex. `not_null<int*>` et `not_null<owner<Foo*>>`) qui ne doit jamais être `nullptr`. `T` peut être tout type pour lequel `==nullptr` a du sens.
span<T> // `[p:p+n)`, constructeur depuis `{p, q}` et `{p, n}` ; `T` est le type de pointeur
span_p<T> // `{p, predicate}` `[p:q)` où `q` est le premier élément pour lequel `predicate(*p)` vaut vrai
Un span<T> désigne zéro ou plusieurs T mutables, à moins que T ne soit de type const. Tous les accès aux éléments du span, notamment via operator[], sont garantis d’être contrôlés par rapport aux limites par défaut.
Note : le
spande la GSL (initialement appeléarray_view) a été proposé pour être intégré dans la bibliothèque standard C++, et a été adopté (avec des changements de nom et d’interface) à l’exception du fait questd::spanne fournit pas de garantie de contrôle des limites. Ainsi, la GSL a modifié le nom et l’interface duspanpour suivrestd::spanet il doit être exactement identique àstd::span, la seule différence étant que lespande la GSL assure pleinement la sécurité des limites par défaut. Si la sûreté des limites devait impacter son interface, ces propositions de modification devraient être repoussées via le comité ISO C++ afin quegsl::spanreste compatible au niveau de l’interface avecstd::span. Si une évolution future destd::spanajoute le contrôle des limites,gsl::spanpourra être supprimé.
L’arithmétique de pointeurs est préférable à l’intérieur des spans.
Un char* qui pointe vers plus d’un char mais ne représente pas une chaîne de caractères de style C (p. ex. : un pointeur dans un tampon d’entrée) devrait être représenté par un span.
zstring // un `char*` censé être une chaîne de caractères de style C ; c’est‑à‑dire une séquence de `char` terminée par zéro ou `nullptr`
czstring // un `const char*` censé être une chaîne de caractères de style C ; c’est‑à‑dire une séquence de `const char` terminée par zéro ou `nullptr`
Logiquement, ces deux derniers alias ne sont pas nécessaires, mais nous ne sommes pas toujours logiques, et ils rendent explicite la distinction entre un pointeur vers un seul char et un pointeur vers une chaîne de caractères de style C.
Une séquence de caractères dont on ne suppose pas qu’elle se termine par zéro devrait être un span<char>, ou, si cela est impossible à cause de problèmes d’ABI, un char*, plutôt qu’un zstring.
Utilisez not_null<zstring> pour des chaînes de caractères de style C qui ne peuvent pas être nullptr. ??? Faut‑il un nom pour not_null<zstring> ? Ou bien son aspect peu esthétique est‑il une caractéristique ?
unique_ptr<T> // possession unique : `std::unique_ptr<T>`
shared_ptr<T> // possession partagée : `std::shared_ptr<T>` (pointeur compté)
stack_array<T> // Un tableau alloué sur la pile. Le nombre d’éléments est déterminé à la construction et fixe par la suite. Les éléments sont mutables sauf si `T` est de type `const`.
dyn_array<T> // Un conteneur, tableau alloué dynamiquement sans agrandissement. Le nombre d’éléments est déterminé à la construction et fixe par la suite.
// Les éléments sont mutables sauf si `T` est de type `const`. En gros, un `span` qui alloue et possède ses éléments.
Expects // assertion de précondition. Placée actuellement dans les corps de fonction. Plus tard, elle devrait être déplacée dans les déclarations.
// `Expects(p)` termine le programme à moins que `p == true`
// `Expects` dépend de certaines options (application, message d’erreur, alternatives à la terminaison)
Ensures // assertion de postcondition. Placée actuellement dans les corps de fonction. Plus tard, elle devrait être déplacée dans les déclarations.
Ces assertions sont actuellement des macros (beurk !) et doivent apparaître dans les définitions de fonctions (seulement) en attente des décisions du comité de normalisation sur les contrats et la syntaxe des assertions.
Voir la proposition de contrat ; en utilisant la syntaxe d’attribut, par exemple, Expects(p) deviendra [[expects: p]].
finally // `finally(f)` crée un `final_action{f}` avec un destructeur qui invoque `f`
narrow_cast<T>(x) // `narrow_cast<T>(x)` équivaut à `static_cast<T>(x)`
narrow<T>(x) // `narrow<T>(x)` équivaut à `static_cast<T>(x)` si `static_cast<T>(x) == x` sans promotion de signe, sinon il lance `narrowing_error` (p. ex. `narrow<unsigned>(-42)` lance une exception)
[[implicit]] // « Marqueur » à placer sur les constructeurs à un argument afin de les rendre explicitement non‑explicites.
move_owner // `p = move_owner(q)` signifie `p = q` mais ???
joining_thread // version RAII de `std::thread` qui se joint automatiquement.
index // type à utiliser pour tous les indexations de conteneurs et de tableaux (actuellement un alias de `ptrdiff_t`)
Ces concepts (prédicats de type) sont empruntés à la bibliothèque Origin d’Andrew Sutton, à la proposition Range, et au TR ISO WG21 Palo Alto. Beaucoup d’entre eux sont très similaires à ce qui a été intégré dans la norme ISO C++ en C++20.
StringNumberBooleanRange // en C++20, std::ranges::rangeSortable // en C++20, std::sortableEqualityComparable // en C++20, std::equality_comparableConvertible // en C++20, std::convertible_toCommon // en C++20, std::common_withIntegral // en C++20, std::integralSignedIntegral // en C++20, std::signed_integralSemiRegular // en C++20, std::semiregularRegular // en C++20, std::regularTotallyOrdered // en C++20, std::totally_orderedFunction // en C++20, std::invocableRegularFunction // en C++20, std::regular_invocablePredicate // en C++20, std::predicateRelation // en C++20, std::relationPointer // Un type avec `*`, `->`, `==` et construction par défaut (la construction par défaut est supposée établir la valeur singulière « null »)
Unique_pointer // Un type qui correspond à `Pointer`, est déplaçable, et n’est pas copiable
Shared_pointer // Un type qui correspond à `Pointer`, et est copiable