C++ Moderne
Guide pour écrire du C++ moderne avec les idiomes C++20, C++23 et C++26. Se concentre sur les motifs qui éliminent les classes de vulnérabilités et réduisent le code répétitif, avec un accent sur la sécurité de Trail of Bits.
Quand utiliser ce skill
- Écrire de nouvelles fonctions, classes ou bibliothèques C++
- Moderniser du code C++ existant (motifs antérieurs à C++20)
- Choisir entre les approches anciennes et modernes
- Travailler sur du C++ critique pour la sécurité ou la sûreté
- Réviser du code C++ pour l'adoption d'idiomes modernes
Quand NE PAS utiliser ce skill
- L'utilisateur demande explicitement une norme plus ancienne : Respectez les contraintes (systèmes embarqués, ABI héritée)
- Code C pur : Ce skill est spécifique au C++
- Questions sur le système de build : La configuration de CMake, Meson, Bazel est hors du cadre
- Projets non C++ : Bases de code mixtes où le C++ n'est pas prédominant
Anti-patrons à éviter
| À éviter | Utiliser à la place | Pourquoi |
|---|---|---|
new/delete | std::make_unique, std::make_shared | Élimine les fuites, les doubles libérations |
| Pointeurs bruts propriétaires | std::unique_ptr, std::shared_ptr | Sémantique de propriété RAII |
Tableaux C (int arr[N]) | std::array<int, N> | Connaissance des bornes, sémantique de valeur |
| Paramètres pointeur + longueur | std::span<T> | Non propriétaire, vérification des bornes |
printf / sprintf | std::format, std::print | Type-safe, pas de débordement de tampon |
Casts de style C (int)x | static_cast<int>(x) | Intention explicite, vérifiable |
Constantes #define | Variables constexpr | De portée, typées, débogables |
SFINAE / enable_if | Concepts + requires | Contraintes et erreurs lisibles |
| Codes d'erreur + paramètres de sortie | std::expected<T, E> | Composable, type-safe |
union | std::variant | Type-safe, pas de UB silencieux |
mutex.lock()/unlock() bruts | std::scoped_lock | Exception-safe, pas de blocage |
std::thread | std::jthread | Auto-jointure, support des jetons d'arrêt |
Macro assert() | contract_assert (C++26) | Visible par les outils, configurable |
| CRTP manuel | Deducing this (C++23) | Plus simple, pas de code répétitif de template |
| Génération de code par macro | Réflexion (C++26) | Zéro surcoût, composable |
Voir anti-patterns.md pour le tableau complet (30+ motifs).
Arbre de décision
Que faites-vous ?
|
+-- Écrire du nouveau code C++ ?
| +-- Utiliser les idiomes modernes par défaut (C++20/23)
| +-- Choisir la norme la plus récente supportée par votre compilateur
| +-- Voir les Niveaux de fonctionnalités ci-dessous
|
+-- Moderniser du code existant ?
| +-- Commencer par les remplacements du Niveau 1 (C++20/23)
| +-- Prioriser par impact sur la sécurité (mémoire > types > style)
| +-- Voir anti-patterns.md pour la table de migration
|
+-- Code critique pour la sécurité ?
| +-- Activer les drapeaux de durcissement du compilateur (voir ci-dessous)
| +-- Activer le mode durci de libc++
| +-- Exécuter les sanitizers dans l'IC
| +-- Voir safe-idioms.md et compiler-hardening.md
|
+-- Utiliser des fonctionnalités C++26 ?
+-- Réflexion : OUI, planifiez pour cela (GCC 16+)
+-- Contrats : avec prudence, pour les nouvelles frontières d'API
+-- std::execution : attendez la maturité de l'écosystème
+-- Voir cpp26-features.md
Niveaux de fonctionnalités
Les fonctionnalités sont classées par utilisabilité pratique aujourd'hui, pas par version de norme.
Niveau 1 : Utiliser aujourd'hui (C++20/23, support solide du compilateur)
| Fonctionnalité | Remplace | Norme |
|---|---|---|
Concepts + requires | SFINAE, enable_if | C++20 |
| Ranges + vues | Boucles d'itérateurs brutes | C++20 |
std::span<T> | Pointeur + longueur | C++20 |
std::format | sprintf, chaînes iostream | C++20 |
Comparaison à trois voies <=> | Opérateurs de comparaison manuels | C++20 |
std::jthread | std::thread + jointure manuelle | C++20 |
| Initialisateurs désignés | Initialisation de struct positionnelle | C++20 |
std::expected<T,E> | Codes d'erreur, exceptions aux frontières | C++23 |
std::print / std::println | printf, std::cout << | C++23 |
Deducing this | CRTP, duplication const/non-const | C++23 |
std::flat_map | std::map pour usage intensif en lecture | C++23 |
std::optional monadique | Vérifications if imbriquées sur les optionals | C++23 |
Voir cpp20-features.md et cpp23-features.md.
Niveau 2 : Déployer maintenant (pas de mise à jour de norme nécessaire)
Ceux-ci améliorent la sécurité sans changer votre version de norme C++ :
- Drapeaux de durcissement du compilateur —
-D_FORTIFY_SOURCE=3,-fstack-protector-strong,-ftrivial-auto-var-init=zero - libc++ durci —
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FASTpour environ 0,3 % de surcoût de vérification des bornes - Sanitizers dans l'IC — ASan + UBSan comme minimum ; TSan pour le code concurrent
- Drapeaux d'avertissement —
-Wall -Wextra -Wpedantic -Werror
Voir compiler-hardening.md.
Niveau 3 : Planifier (C++26, vaut la peine de se restructurer autour)
La réflexion est la fonctionnalité C++26 la plus transformatrice. Elle élimine :
- Le code répétitif de sérialisation (une fonction générique remplace le
to_jsonpar structure) - Les générateurs de code (codegen protobuf, Qt MOC)
- Les hacks d'enregistrement par macros et de conversion énumération-texte
GCC 16 (avril 2026) a fusionné la réflexion. Planifiez le nouveau code pour en bénéficier.
Niveau 4 : Surveiller (C++26, nécessite maturation)
- Contrats (
pre/post/contract_assert) — Meilleurs queassert(), mais pas de support pour les fonctions virtuelles et support limité du compilateur. Adoptez avec prudence pour les nouvelles frontières d'API. - std::execution (émetteurs/récepteurs) — Framework asynchrone puissant, mais courbe d'apprentissage abrupte, aucun ordonnanceur fourni, et documentation pauvre. Attendez la maturité de l'écosystème.
Voir cpp26-features.md.
Référence rapide pour le durcissement du compilateur
Drapeaux essentiels (GCC + Clang)
-Wall -Wextra -Wpedantic -Werror
-D_FORTIFY_SOURCE=3
-fstack-protector-strong
-fstack-clash-protection
-ftrivial-auto-var-init=zero
-fPIE -pie
-Wl,-z,relro,-z,now
Spécifique à Clang
-Wunsafe-buffer-usage
libc++ durci (Clang/libc++ uniquement)
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
Google a déployé cela dans Chrome et sa flotte de serveurs : environ 0,3 % de surcoût, plus de 1000 bogues trouvés, réduction de 30 % des segfaults en production.
Voir compiler-hardening.md pour le guide complet.
Rationalisations à rejeter
| Rationalisation | Pourquoi c'est faux |
|---|---|
| "Ça compile sans avertissements" | Les avertissements dépendent des drapeaux activés. Ajoutez -Wall -Wextra -Wpedantic. |
| "ASan est trop lent pour la production" | Utilisez GWP-ASan pour la détection basée sur l'échantillonnage en production (~0 % de surcoût). |
| "Nous n'utilisons que des conteneurs sûrs" | L'invalidation d'itérateur et l'accès non vérifié à optional sont toujours exploitables. |
| "Les pointeurs intelligents sont plus lents" | std::unique_ptr a un surcoût nul par rapport aux pointeurs bruts. Mesurez avant d'affirmer. |
| "Notre code n'a pas de bogues mémoire" | Google a trouvé plus de 1000 bogues en activant libc++ durci. Tout le monde aussi. |
| "Les fonctionnalités C++26 ne sont pas encore disponibles" | Les fonctionnalités C++20/23 le sont. Les drapeaux de durcissement fonctionnent sur n'importe quelle norme. Commencez là. |
| "Le C++ moderne est plus difficile à lire" | std::expected est plus lisible que de vérifier des codes d'erreur sur 5 paramètres de sortie. |
Liste de contrôle des meilleures pratiques
- Utiliser des pointeurs intelligents pour la propriété, les pointeurs bruts uniquement pour l'observation non propriétaire
- Préférer
std::spanà pointeur + longueur pour les paramètres de fonction - Utiliser
std::expectedpour les fonctions qui peuvent échouer avec des erreurs typées - Contraindre les templates avec des concepts, pas SFINAE
- Activer les drapeaux de durcissement du compilateur et libc++ durci dans toutes les constructions
- Exécuter ASan + UBSan dans l'IC ; ajouter TSan pour le code concurrent
- Utiliser
constexpr/constevalquand c'est possible (sans UB par conception) - Marquer les fonctions
[[nodiscard]]lorsque ignorer la valeur de retour est probablement un bogue - Préférer la sémantique de valeur ; utiliser
std::variantau lieu deunion,enum classau lieu deenum - Initialiser toutes les variables à la déclaration
Lire ensuite
- anti-patterns.md — Table de migration complète héritage vers moderne (30+ motifs)
- cpp20-features.md — Concepts, ranges, span, format, coroutines
- cpp23-features.md — expected, print, deducing this, flat_map
- cpp26-features.md — Réflexion, contrats, améliorations de la sécurité mémoire
- compiler-hardening.md — Drapeaux, sanitizers, libc++ durci
- safe-idioms.md — Motifs de sécurité par classe de vulnérabilité