Les innovations de C++26 : comment les dernières améliorations vont transformer le développement en C++,
dans un contexte où plusieurs entités recommandent de remplacer C++ par des alternatives
Le langage de programmation C++ continue d'évoluer avec l'introduction de nouvelles fonctionnalités et améliorations. La version C++26, bien que toujours en cours de développement, apporte déjà plusieurs nouveautés : spécifier une raison pour la suppression d'une fonction, variables de remplacement sans nom, déclaration d'une liaison structurée en tant que condition. Mais certains encouragent plutôt le passage à Rust ou Carbon, pourquoi ?
Spécifier une raison pour la suppression d'une fonction
Depuis le C++11, il est possible de déclarer une fonction comme supprimée, afin que le compilateur empêche son utilisation. Cela peut être utilisé pour empêcher l'utilisation de fonctions membres spéciales d'une classe, mais aussi pour supprimer toute autre fonction.
Introduits en C++11, = default et = delete ont rejoint = 0 en tant que spécification alternative possible pour le corps d'une fonction, au lieu d'un corps d'instructions ordinaire entouré d'accolades. La motivation initiale de la déclaration de fonction supprimée via = delete est de remplacer (et d'annuler) la pratique courante de l'ère C++98/03 consistant à déclarer les fonctions membres spéciales comme privées et à ne pas les définir afin de désactiver leur génération automatique. Cependant, l'ajout de = delete a acquis un pouvoir encore plus grand, car il peut être utilisé pour n'importe quelle fonction, et pas seulement pour les membres spéciaux.
Aujourd'hui, dix ans après l'introduction des fonctions supprimées, nous pouvons conclure en toute confiance que = delete est devenu l'une des fonctionnalités clés de C++11 qui a grandement amélioré l'expérience des utilisateurs en cas d'erreurs dans l'utilisation des fonctions de la bibliothèque et a été une réussite de la révolution du « C++ moderne ».
Il y a plusieurs raisons pour lesquelles les fonctions supprimées ont été préféré aux fonctions traditionnelles privées mais non définies, notamment une meilleure sémantique (friend et les autres membres sont toujours inaccessibles, ce qui transforme une erreur de l'éditeur de liens en une erreur à la compilation), de meilleurs diagnostics (au lieu d'erreurs cryptiques « fonction inaccessible », l'utilisateur sait directement que la fonction est supprimée) et une plus grande puissance (pas seulement pour les SMF).
Au lieu d'une erreur déjà plus conviviale mais toujours un peu énigmatique « calling deleted function », les éditeurs veulent permettre directement aux auteurs de bibliothèques de présenter un message supplémentaire facultatif qui devrait être inclus dans le message d'erreur, de sorte que l'utilisateur connaisse le raisonnement exact de la raison pour laquelle la fonction est supprimée, et dans certains cas, vers quel remplacement l'utilisateur devrait se diriger à la place.
Une fonction peut être supprimée comme suit (exemple tiré du document de proposition) :
1 2 3 4 5 6 7 8 9 10
| class NonCopyable
{
public:
// ...
NonCopyable() = default;
// les membres de la copie
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
}; |
En C++26, vous pouvez spécifier la raison pour laquelle cette fonction est supprimée :
1 2 3 4 5 6
| class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete("Cette classe gère des ressources uniques, la copie n'est pas supportée; utilisez le déplacement à la place.");
NonCopyable& operator=(const NonCopyable&) = delete("Cette classe gère des ressources uniques, la copie n'est pas supportée; utilisez le déplacement à la place.");
}; |
Variables de remplacement sans nom
Il existe des cas où une variable doit être déclarée sans que son nom soit utilisé, comme dans les liaisons de structure ou les verrous (lock_guard). C++26 introduit la possibilité d’utiliser un seul trait de soulignement (_) pour définir une variable sans nom.
Par exemple, dans l'exemple suivant, unused est une variable qui n'est pas utilisée :
[[maybe_unused]] auto [data, unused] = get_data();
En C++26, la variable unused peut être nommée _ (simple trait de soulignement) :
auto [data, _] = get_data();
Lorsque l'identificateur de soulignement unique est utilisé pour la déclaration d'une variable, d'une variable membre de classe non statique, d'une capture lambda ou d'une liaison de structure, l'attribut [[maybe_unused]] est implicitement ajouté, il n'est donc pas nécessaire de l'utiliser explicitement.
Une déclaration portant le nom _ est dite indépendante du nom si elle déclare :
- une variable avec une durée de stockage automatique
- une liaison de structure, mais pas dans un espace de noms
- une variable introduite par une capture init
- un membre de données non statique
Le compilateur n'émet pas d'avertissement quant à l'utilisation ou non d'une déclaration indépendante du nom. De plus, plusieurs déclarations indépendantes du nom peuvent être utilisées dans la même portée (qui n'est pas une portée d'espace de noms) :
1 2 3 4 5 6 7
| int main()
{
int _;
_ = 0; // OK
std::string _; // OK, parce que _ est une déclaration indépendante du nom
_ = "0"; // Erreur : référence ambiguë au caractère générique « _ », qui est défini plusieurs fois.
} |
En revanche, ce qui suit n'est pas possible :
1 2 3 4 5 6
| int main()
{
int _;
_ = 0; // OK
static std::string _; // Erreur : les variables statiques ne sont pas indépendantes du nom
} |
L'opération suivante n'est pas non plus possible, car les déclarations se trouvent dans un espace de noms :
1 2 3 4 5 6
| namespace n
{
int f() {return 42;}
auto _ = f(); // OK
auto _ = f(); // Erreur : redéfinition de _
} |
Déclaration d'une liaison structurée en tant que condition
Une liaison de structure définit un ensemble de variables liées à des sous-objets ou à des éléments de leur initialisateur.
auto [position, length] = get_next_token(text, offset);
Une liaison de structure peut apparaître dans une déclaration for-range, comme dans l'exemple suivant :
1 2 3 4
| for (auto [position, length] : tokenize(text, offset))
{
std::println("pos {}, len {}", position, length);
} |
En revanche, les variables peuvent apparaître dans la condition d'une instruction if, while ou for :
1 2 3 4
| if (auto it = std::find_if(begin(arr), end(arr), is_even); it != std::end(arr))
{
std::println("{} est le premier nombre pair", *it);
} |
Cependant, les liaisons de structure ne peuvent pas être déclarées dans la condition d'une instruction if, while ou for. Cela change en C++26, ce qui rend la chose possible :
1 2 3 4
| if(auto [position, length] = get_next_token(text, offset); position >= 0)
{
std::println("pos {}, len {}", position, length);
} |
Un cas intéressant et très utile est présenté dans le document de proposition (P0963). Considérons l'exemple C++26 suivant pour l'utilisation de std::to_chars :
1 2 3 4 5 6 7 8 9 10
| if (auto result = std::to_chars(p, last, 42))
{
auto [ptr, _] = result;
// ok pour continuer
}
else
{
auto [ptr, ec] = result;
// gestion des erreurs
} |
Lorsque la fonction réussit, seul le membre ptr de std::to_chars_result nous intéresse, car il contient un pointeur sur le pointeur de fin de ligne des caractères écrits. Si la fonction échoue, nous devons également examiner le membre ec (du type std::errc) qui représente un code d'erreur.
Ce code peut être simplifié avec des liaisons de structure, en C++26, comme suit :
1 2 3 4 5 6 7 8
| if (auto [ptr, ec] = std::to_chars(p, last, 42))
{
// ok pour continuer
}
else
{
// gestion des erreurs
} |
Le projet Carbon, un successeur expérimental du C++, explore une direction future possible pour le C++ étant donné les difficultés à l'améliorer
En février 2020, un vote crucial a eu lieu au sein du comité de normalisation du C++ sur la rupture de la compatibilité ABI en faveur de la performance. L’initiative principalement poussée par les employés de Google a échoué. Résultat : de nombreux Googlers ont cessé de participer à la normalisation du C++, ont démissionné de leur rôle officiel au sein du comité, et le développement de clang a considérablement ralenti. C’est de cette rupture que naît le projet Carbon annoncé comme successeur du C++. L’objectif : explorer une direction future possible pour le C++ étant donné les difficultés à l’améliorer. Le projet Carbon mise sur l’interopérabilité avec le C++ comme base de travail.
Les développeurs de Carbon expliquent que certes, le C++ est le langage dominant pour les logiciels à performances critiques, mais son héritage et sa dette technique signifient que son amélioration incrémentale est une tâche très ardue. Carbon est un nouveau langage qui vise à égaler les performances de C++ et à maintenir une interopérabilité bidirectionnelle transparente, ainsi qu'une courbe d'apprentissage douce pour les développeurs C++.
L'équipe promet en sus un certain niveau de traduction de source à source pour le code C++. Le projet présente des parallèles avec TypeScript pour les développeurs JavaScript, ou Kotlin pour les développeurs Java, bien que la comparaison ne soit pas exacte. Carbon est conçu pour être interopérable avec le code C++ et pour faciliter la migration. La chaîne d'outils Carbon prendra en charge la compilation du code C++.
Pourquoi le C++ est-il difficile à améliorer ? Parce que le langage lui-même a commencé comme une bifurcation du C. Selon l'équipe Carbon, les concepteurs du C++ ont ajouté plutôt que remplacé des fonctionnalités du langage au fil du temps, créant ainsi des interactions complexes entre les fonctionnalités. La préservation de la compatibilité binaire est un autre problème hérité. En outre, le comité C++ et le processus d'évolution sont orientés vers la normalisation plutôt que la conception, sont lents et ne parviennent pas toujours à prendre des décisions.
Carbon s'efforce de contourner ces problèmes en adoptant une nouvelle approche fondée sur les principes de l'open source. « Nous tenterons même de combler une énorme lacune dans l'écosystème C++ avec un gestionnaire de paquets intégré », peut-on lire dans les documents.
La Maison Blanche invite les développeurs à abandonner le C et le C++ pour passer à des langages comme le Rust
Faut-il arrêter d’initier de nouveaux projets en C ou C++ et passer à Rust ? La question divise dans la communauté des développeurs dont certains recommandent le langage Rust plutôt que le C ou le C++. Les raisons : la parité du Rust en termes de vitesse d’exécution en comparaison avec le C ; la sécurisation et la fiabilité du Rust en comparaison avec C ou C++. La comparaison entre Rust et C++ vient de prendre un coup de neuf avec un rapport de la Maison Blanche sur la sécurisation de la mémoire qui invite les développeurs à abandonner C ou C++ pour passer à des langages comme le Rust jugés supérieurs pour sécuriser les espaces mémoire des logiciels. C’est une sortie qui fait suite à la prise de position du créateur du langage C++ selon laquelle : « la sécurisation des logiciels par le Rust n’est pas supérieure à celle offerte par le C++. »
Sources : OpenSTD (1, 2, 3), Carbon
Et vous ?
Quelle est votre fonctionnalité préférée parmi celles introduites dans C++26 et pourquoi ?
Comment pensez-vous que la possibilité de spécifier une raison pour la suppression d’une fonction pourrait améliorer le développement en C++ ?
Avez-vous déjà rencontré des situations où les variables de remplacement sans nom seraient particulièrement utiles ? Pouvez-vous partager un exemple ?
Pensez-vous que ces nouvelles fonctionnalités de C++26 vont simplifier ou compliquer le processus de développement ? Pourquoi ?
Quels autres aspects du langage C++ aimeriez-vous voir améliorés dans les futures versions ?
Comment ces nouveautés de C++26 se comparent-elles aux améliorations récentes d’autres langages de programmation que vous utilisez ?
Voyez-vous des défis potentiels à l’adoption de ces nouvelles fonctionnalités dans des projets existants ? Si oui, lesquels ?
Comment ces améliorations pourraient-elles influencer votre approche de la programmation en C++ ?
Que pensez-vous des projets comme Carbon ou des propositions comme celle de la Maison Blanche visant à se délester du C++ ?
Voir aussi :
Google affirme qu'il est non seulement possible, mais aussi relativement facile de remplacer C++ par Rust dans les firmwares. L'entreprise explique en quoi ce changement est avantageux
« Les équipes Rust chez Google sont deux fois plus productives que celles qui se servent de C++ », d'après un responsable de l'entreprise qui lance le débat de la productivité entre Rust et C++
Partager