Depuis 2011, de nombreuses fonctionnalités ont été ajoutées à la norme C++. Certaines, comme auto et nullptr, sont aujourd'hui largement utilisées, tandis que d'autres restent relativement peu répandues. Cependant, certains des changements les plus significatifs — ceux qui pourraient faire passer le langage au niveau supérieur — sont encore en gestation.
Modules
Le mécanisme hérité
#includeest toujours là, et il présente de nombreux inconvénients. En voici quelques-uns, tirés de ce document.
- intéressant. Passage à l'échelle du temps de compilation: chaque fois qu'un en-tête est inclus, le compilateur doit prétraiter et analyser le texte de cet en-tête et de tous les en-têtes qu'il inclut, transitivement. Ce processus doit être répété pour chaque unité de traduction de l'application, ce qui représente une énorme quantité de travail redondant. Dans un projet comportant Nunités de traduction et M en-têtes inclus dans chaque unité de traduction, le compilateur effectue M x N de travail alors que la plupart des M en-têtes sont partagés entre plusieurs unités de traduction. Le C++ est particulièrement mauvais à ce jeu, car le modèle de compilation des templates oblige à placer une énorme quantité de code dans les en-têtes.
- Fragilité: #include: les directives sont traitées comme une inclusion textuelle par le préprocesseur et sont donc soumises à toutes les définitions de macros actives au moment de l'inclusion. Si l'une des définitions de macros actives entre en collision avec un nom de la bibliothèque, cela peut casser l'API de la bibliothèque ou provoquer des échecs de compilation dans l'en-tête de la bibliothèque elle-même. Pour un exemple extrême, #define std "The C++ Standard" puis incluez un en-tête de la bibliothèque standard : le résultat est une horrible cascade d'échecs dans l'implémentation de la bibliothèque standard C++. Des problèmes réels plus subtils surviennent lorsque les en-têtes de deux bibliothèques différentes interagissent à cause de collisions de macros, et que les utilisateurs sont forcés de réordonner les directives #include ou d'introduire des directives #undef pour rompre la dépendance (involontaire).
- Solutions de contournement conventionnelles: les programmeurs C ont adopté un certain nombre de conventions pour contourner la fragilité du modèle de préprocesseur du C. Les gardes d'inclusion, par exemple, sont nécessaires pour la grande majorité des en-têtes afin de garantir qu'une inclusion multiple ne casse pas la compilation. Les noms de macros sont écrits avec desIDENTIFIANTS_LONGS_EN_MAJUSCULES_PREFIXES pour éviter les collisions, et certains développeurs de bibliothèques/frameworks utilisent même des noms __soulignés dans les en-têtes pour éviter les collisions avec les noms « normaux » qui (par convention) ne devraient même pas être des macros. Ces conventions constituent une barrière à l'entrée pour les développeurs venant de langages autres que C, du code répétitif pour les développeurs plus expérimentés, et rendent nos en-têtes bien plus laids qu'ils ne devraient l'être.
- Confusion des outils: dans un langage basé sur C, il est difficile de construire des outils qui fonctionnent bien avec les bibliothèques logicielles, car les frontières des bibliothèques ne sont pas claires. Quels en-têtes appartiennent à une bibliothèque donnée, et dans quel ordre ces en-têtes doivent-ils être inclus pour garantir qu'ils compilent correctement ? Les en-têtes sont-ils C, C++, Objective-C++, ou l'une des variantes de ces langages ? Quelles déclarations de ces en-têtes sont réellement destinées à faire partie de l'API, et quelles déclarations ne sont présentes que parce qu'elles ont dû être écrites dans le fichier d'en-tête ?
Les modules améliorent l'accès aux bibliothèques avec un modèle sémantique plus robuste et plus efficace. Du point de vue de l'utilisateur, le code ne change que légèrement, puisqu'on utilise une déclaration
importplutôt qu'une directive de préprocesseur
#include:
import std; // Module import directive.
int main() {
std::cout << “Hello World\n”;
}
Plus besoin d'inclure de nombreux en-têtes STL — un seul import suffit, ce qui donne un code plus propre. L'import de module charge une représentation binaire du module
stdet rend son API directement disponible pour l'application. Les définitions du préprocesseur qui précèdent la déclaration d'import n'ont aucun impact sur l'API fournie par
std, car le module lui-même a été compilé comme un module autonome et séparé.
MetaClass
Herb Sutter contribue à l'amélioration du C++ depuis de nombreuses années, et l'an dernier il a proposé la fonctionnalité des métaclasses. Voici la motivation qui la sous-tend, tirée de son billet:
: Je travaille sur une nouvelle fonctionnalité expérimentale du langage C++, provisoirement appelée « métaclasses », qui vise à rendre la programmation C++ à la fois plus puissante et plus simple.
Et voici une définition des métaclasses et des bénéfices de leur utilisation, tirée de la proposition:
: Les métaclasses (nom provisoire) permettent aux programmeurs d'écrire un nouveau type d'abstraction efficace : un sous-ensemble nommé et défini par l'utilisateur de classes partageant des caractéristiques communes — y compris des règles, des valeurs par défaut et des fonctions générées définies par l'utilisateur — en écrivant une transformation personnalisée de code source C++ normal vers une définition de classe C++ normale. Il n'y a pas de bifurcation du système de types ; la classe générée est une classe normale. Objectifs principaux : • Élargir le vocabulaire d'abstraction du C++ au-delà de class/struct/union/enum, qui sont les catégories de types codées en dur dans le langage. • Permettre de fournir les bonnes pratiques de longue date sous forme de bibliothèques réutilisables plutôt que de guides/livres en langage naturel, pour disposer d'un vocabulaire facilement adopté (par exemple interface, value) au lieu de listes de règles à mémoriser (par exemple retenir tel motif de codage pour écrire une classe de base abstraite ou un type valeur, en comptant sur des outils pour trouver les erreurs). • Permettre d'écrire des motifs appliqués par le compilateur à toutes fins : standards de codage (par exemple les nombreuses règles « enforce » des Core Guidelines), exigences d'API (par exemple les règles qu'une classe doit suivre pour fonctionner avec une bibliothèque d'interface matérielle, une extension de navigateur, un mécanisme de rappel), et tout autre motif pour les classes. • Permettre d'écrire de nombreuses nouvelles fonctionnalités de « types spécialisés » (comme nous l'avons fait en C++11 avec enum class) sous forme de code de bibliothèque ordinaire plutôt qu'en pseudo-anglais normatif, avec une utilisabilité et une efficacité égales, afin qu'elles puissent être testées unitairement et déboguées avec des outils normaux, développées/distribuées sans mettre à jour ni livrer un nouveau compilateur, et passer par LEWG/LWG sous forme de code plutôt que par EWG/CWG sous forme de texte normatif. En conséquence, permettre de standardiser des extensions précieuses que nous ne standardiserions probablement jamais dans le langage noyau parce qu'elles sont trop spécialisées (par exemple interface), mais que nous pourrions facilement standardiser comme une petite bibliothèque autonome. • Éliminer le besoin d'inventer des « langages annexes » non-C++ et des compilateurs spéciaux, tels que Qt moc, COM MIDL et C++/CX, pour exprimer les informations dont leurs systèmes ont besoin mais qui ne peuvent être exprimées dans le C++ actuel (comme les types spécialisés pour les propriétés, les rappels d'événements et autres abstractions similaires).
Avec les modules et les métaclasses, le C++ pourrait devenir à la fois plus puissant et plus facile à utiliser, avec de nombreuses autres fonctionnalités susceptibles d'être ajoutées à l'avenir. Tout ce que nous pouvons dire, c'est : merci à tous les contributeurs du C++ qui continuent d'améliorer le langage — et longue vie au C++ ! :)
