Nous avons lu pour vous : Embracing Modern C++ Safely

Posté par  (site web personnel) . Édité par small_duck, Lawless, alkino, pulkomandy, serge_sans_paille et Ysabeau 🧶 🧦. Modéré par Ysabeau 🧶 🧦. Licence CC By‑SA.
Étiquettes :
27
9
sept.
2022
C et C++

Embracing Modern C++ Safely par John Lakos, Vittorio Romeo, Rostislav Khlebnikov, et Alisdair Meredith, est un livre couvrant les nouveautés apportées au langage C++ dans ses versions 11 et 14. Les auteurs sont tous employés de Bloomberg et présentent chaque addition au langage sous couvert de l’expérience qu’ils en ont eu au quotidien dans leur métier.

Sommaire

Présentation des auteurs

John Lakos travaille depuis 20 ans chez Bloomberg, en tant que chef d’une division qui développe des composants C++ de base utilisés en interne. Il participe régulièrement à des conférences sur le C++, et fait partie du comité de normalisation du C++.

Vittorio Romeo, Rostislav Khlebnikov, et Alisdair Meredith sont tous trois développeurs senior chez Bloomberg. Le premier s’investit énormément dans la communication autour du C++ et fait de nombreuses présentations très pédagogiques sur ce sujet. On en trouvera des enregistrements sur sa page YouTube. John Lakos, Vittorio Romeo et Alisdair Meridith sont aussi auteurs ou co-auteurs de nombreuses propositions soumises au comité décidant des évolutions du standard C++.

Bloomberg est une gigantesque multinationale de la finance de 15 000 employés, spécialisée dans la centralisation et la publication de données financières, comme les cours de bourse, les nouvelles financières, les informations sur les entreprises. Leur produit phare est le « terminal Bloomberg », autrefois du matériel spécialisé mais aujourd’hui une simple application qui tourne sur un PC, et qui ressemble furieusement à un super-minitel. Bloomberg est donc connecté à la grande majorité des places financières de la planète, et redistribue les données en temps réel ou permet des requêtes historiques.

Ils sont connus dans le métier pour avoir une énorme base de code en C++. Ils sont également connus dans le métier pour avoir une base de code plutôt ancienne avec beaucoup de code pas franchement à la pointe de la modernité, ce qui explique certainement l’intérêt de nos quatre auteurs à soutenir les bonnes pratiques et à évangéliser une approche moderne et sûre.

Pourquoi un livre sur C++14 en 2021?

Les standards C++11 et 14 ne sont plus tout récents, les versions 17 et 20 sont déjà disponibles.

Cependant, l’objectif de ce livre est de fournir un retour d’expérience après avoir vraiment pratiqué la programmation C++ avec ces fonctionnalités en long, en large et en travers.

Cela a donc nécessité l’implémentation des fonctionnalités dans un compilateur, le déploiement de ce compilateur dans un environnement de production, puis encore quelque temps pour la prise en main et l’utilisation des fonctionnalités et la découverte des problèmes qui peuvent survenir lors d’une utilisation réelle, hors d’exemples souvent simplistes fournis dans la documentation existante.

Contenu du livre

Andrei Alexandrescu, auteur de nombreux livres sur le C++, l’explique très bien dans la préface : ce livre est avant tout un diff du C++98 avec le C++14. Ce n’est donc ni un tutoriel, ni un recueil des meilleures pratiques de programmation, et il contient finalement assez peu de conseils. Au lieu de cela, c’est un inventaire exhaustif des nouvelles fonctionnalités du langage, expliquées et décortiquées, en particulier à travers le prisme de la sécurité.

Pour chaque fonctionnalité, le livre explique en détail son fonctionnement, puis fournit des cas courants d’utilisation, les erreurs à éviter, et enfin les cas particuliers gênants à connaître. Parfois, une annexe est ajoutée à la fin de la section pour décrire un concept plus en détail.

Il ne s’agit donc pas d’un livre pour apprendre le C++, il s’adresse plutôt aux personnes qui connaissent déjà le langage. Par exemple, il peut servir de référence à quelqu’un qui devrait rédiger des règles de style pour une entreprise et décider de la « bonne » façon d’utiliser ou pas ces fonctionnalités. Les auteurs ont constaté que les règles appliquées chez Bloomberg ou dans d’autres entreprises étaient parfois arbitraires, basées sur les préférences personnelles de la personne qui les a rédigées, où parfois basées sur des problèmes obsolètes (bug dans une ancienne version d’un compilateur).

Avec ce livre, on dispose de toutes les raisons pour ou contre l’utilisation de chaque fonctionnalité du langage, ce qui permet d’établir des règles à partir de critères moins subjectifs.

Le livre est énorme. Pas loin de 1 400 pages pour 18,5 × 23 × 5 cm ; il pèse plus de deux kilos. L’intérieur est aéré, bien lisible, et les exemples de code sont bien intégrés et légers.

Le contenu du livre est organisé d’une manière ingénieuse, en trois grandes parties regroupant chacune des fonctionnalités du langage considérées sûres, variablement sûres, et risquées. Il faut entendre le terme « sûr » comme une indication de la réponse à la question « si j’utilise la fonctionnalité X, risquerai-je d’avoir de mauvaises surprises ? ». Les mauvaises surprises pouvant aller de « le code est plus compliqué à maintenir » à « certains cas sont undefined behavior ou crashent ».

L’un des auteurs (Romeo) propose également d’utiliser cette classification pour enseigner le C++. Les deux premières catégories peuvent être expliquées dans une formation de base sur C++ : la première sans conditions, la deuxième en prenant soin d’expliquer où se trouvent les pièges potentiels. Les fonctionnalités rangées dans la troisième catégorie seront à réserver à des besoins très spécifiques, à des programmeurs plus expérimentés, et méritent chacune une formation ou un atelier spécifique pour bien les comprendre et les utiliser correctement.

La section des fonctionnalités sûres liste : les attributsles > consécutifsdecltype — les fonctions marquées defaultla délégation de constructeurs — les fonctions marquées delete — les opérateurs explicit — l’utilisation de static dans une fonction — les types anonymes ou locaux en paramètres template — le type long long — l’attribut [[noreturn]]nullptroverride — les chaînes brutesstatic_assert — le type de retour en fin de signature de fonction — les littéraux unicodeusing en tant qu’alias de type — l'initialisation de types agrégés — les littéraux binaires avec 0b — l’attribut [[deprecated]] — le séparateur de chiffres ' — les variables template.

Dans partie des fonctionnalités conditionnellement sûres on trouvera : alignasalignof — les variables auto — l’initialisation avec des accolades — les fonctions ou variables constexpr — la valeur d'initialisation par défaut des membres de classes et d'unions — enum classextern template — les forwarding references — les POD généralisés — l'héritage de constructeursstd::initializer_list — les lambdas — l'opérateur noexcept — les enumérations opaques — range forstd::move et les références aux rvalues — la définition du type sous-jacent aux énumérations — les littéraux définis par l’utilisateur — les templates variadiques.

Enfin la section des fonctionnalités risquées liste : l’attribut [[carries_dependency]]final — les déclarations friend étenduesinline namespace — le spécificateur noexcept — les fonctions membres marquées de & ou && — les unions avec des types non triviaux — la déduction automatique du type de retour d’une fonction — decltype(auto).

Les lecteurs attentifs auront remarqué que ces listes concernent uniquement le langage C++ à proprement parler. Le livre n’aborde pas les nombreux changements dans la STL, la bibliothèque standard de fonctions qui fait également partie de la spécification du C++.

À l’heure où j’écris ces lignes, j’ai lu environ la moitié du livre. Chaque fonctionnalité est soigneusement présentée, suivie de cas typiques d’utilisation. Puis viennent les pièges éventuels et les situations ennuyeuses ou gênantes.

Par exemple, pour prendre un cas sûr, l’utilisation du mot-clé static dans la déclaration d’une variable locale à une fonction garantit que l’initialisation sera faite de manière atomique depuis C++11. Voici un extrait tiré du livre :

Logger& getLogger()
{
  // Même si deux threads appellent getLogger() simultanément, local est bien
  // initialisé une seule fois.
  static Logger local("log.txt");
  return local;
}

Pour illustrer un potentiel piège, considérons maintenant ce cas d’utilisation de getLogger():

struct FileManager
{
  FileManager()
  {
    getLogger() << "Démarrage du gestionnaire de fichiers…";
    // …
  }

  ~FileManager()
  {
    getLogger() << "Extinction du gestionaire de fichiers…";
    // …
  }
};

FileManager& getFileManager()
{
  static FileManager fileManager;
  return fileManager;
}

L'ordre des premiers appels à getFileManager() et getLogger() n'a pas d'importance. Si le premier est appelé avant le second, il déclenchera la création du logger, sinon le logger aura déjà été créé. Par contre, l'ordre de destruction est important (notez que l'ordre de destruction est forcément l'inverse de l'ordre de création).

  • si FileManager est détruit en premier, tout se passe bien.
  • autrement, le comportement du programme est non spécifié (undefined behavior) car le destructeur de FileManager va appeler getLogger() qui va retourner une référence vers un objet qui n’existe plus.

Note de votre serviteur : ici le souci est lié à l’utilisation d’une variable statique dans les fonctions et non pas lié spécifiquement aux ajouts de C++11.

Au niveau des gênes, les auteurs notent à juste titre que dans le cas d’une fonction utilisée dans un contexte mono-thread, l’initialisation d’une variable locale static sera quand même inutilement gardée par des primitives de synchronisation, et l’appel ne pourra être inline (Notons que l’option -fno-threadsafe-statics permet de contrôler cet aspect avec GCC et Clang).

Du côté des fonctionnalités sans inconvénient, on peut noter nullptr qui n’a aucun piège ni ne provoque de surprise, ou encore override, qui améliore grandement la qualité du code et aide à éviter de nombreuses erreurs, sans qu’il soit vraiment possible de se tromper.

Enfin, s’agissant d’une première édition, le livre contient quelques menues erreurs ici et là, bénignes et qui seront sans doute remarquées par les habitués du langage. Elles pourront cependant perturber les autres lecteurs ; pour ces derniers, il vaut sans doute mieux attendre la seconde édition avant d’aborder ce livre.

Commentaires du lecteur

Commentaires de Julien Jorge

Cet ouvrage est un peu comme le livre que j’écris mais rédigé en plus détaillé, mieux renseigné, et beaucoup plus neutre, par des gens qui en savent bien plus que moi. C’est un peu frustrant, mais je suis content qu’Embracing Modern C++ Safely existe :)

Je considère que c’est un très bon achat et je vous recommande de l’acquérir, que ce soit pour vous mettre à jour si vous n’utilisez pas encore ou à peine C++11, ou bien pour profiter de l’expérience de gens très qualifiés si vous voulez optimiser votre utilisation du langage. Chaque section est très complète et les avantages et inconvénients sont bien expliqués, avec des exemples simples.

On pourra reprocher au livre d’être un peu trop verbeux. Il n’est pas rare de lire une information, puis de la relire sous forme de code et commentaire, puis de la lire encore une fois deux pages plus loin. Le fait que le livre ait autant de pages que le standard C++14 est déjà un indicateur. Cela dit, je vous conseille de lire ce livre plutôt que le standard. Il faut noter que le livre ne couvre que les additions au langage et n’aborde pas la bibliothèque standard.

Cette verbosité est cependant un avantage pour le lecteur qui souhaite piocher dans le livre dans le désordre. En effet, chaque section est indépendante et peut être abordée directement.

Je note l’intention des auteurs d’être factuels et d’éviter tout forme d’opinion. C’est tout à fait réussi. Par exemple, dans les pièges potentiels et autres gênes, les situations sont listées simplement sans indiquer de gravité absolue ni même relative des uns par rapport aux autres. Certains de ces exemples me semblent parfois tirés par les cheveux et il m’arrive de me demander « mais qui irait faire ça ? » devant certains codes. Néanmoins, puisque les auteurs souhaitent présenter des faits, il est bien vu de leur part de présenter tous ces exemples sans discrimination.

Commentaires de small_duck - Un ouvrage intéressant, mais un peu tardif ?

Je ne peux qu’applaudir l’effort et l’exhaustivité de l’ouvrage, qui reprend en détail chaque nouvelle fonctionnalité, ce que j’avais tenté de faire à mon humble niveau dans la section C++ de mon blog. Les sections de cas d’utilisation sont certainement celles que je trouve les plus intéressantes : une nouvelle fonctionnalité, d’accord, mais à quoi ça sert, et pourquoi ? Les explications sont donc très claires, même si j’aurais justement préféré que ces sections prennent le pas sur les risques, qui à mon avis prennent trop de place dans le bouquin. Quelques conseils auraient été les bienvenus: les auteurs se bornent trop souvent à indiquer que « nous ne recommandons pas particulièrement cette fonctionnalité, juste indiquons son existence. »

La question se pose tout de même : en 2022, à qui s’adresse le livre ? À des développeurs et des développeuses chevronnés, très à la pointe dans le C++ 98, mais qui n’auraient pas ou peu fait de C++11/14 ? Avec la myriade de tutoriels, pages StackOverflow ou encore les dernières pages Guru of the week d’Herb Sutter, quels sont les développeurs ou les développeuses qui ont attendu ce livre pour migrer leur base de code ?

Aujourd’hui, quelqu’un cherchant un ouvrage sur le C++ moderne pourrait plutôt se laisser tenter par un livre qui irait jusqu’au dernier standard, peut-être en laissant de côté les fonctionnalités les plus obscures, avec des éléments de bibliothèque standard, peut-être dans une approche moins scolaire et plus prescriptive.

J’y trouve cependant mon bonheur, en partant du début du livre et en lisant petit à petit, pour réviser mes fonctionnalités, apprendre de nouvelles techniques que je pourrais utiliser dans mes bases de code, ou m’assurer que je les utilise correctement.

Où trouver le livre

Plusieurs librairies proposent le livre. On trouvera une liste sur le site officiel. Il vous en coûtera une cinquantaine d’euros pour l’acquérir.

Aller plus loin

  • # C++ oh mon cher vieux C++ ...

    Posté par  (site web personnel) . Évalué à 5.

    C'est fou comme, une fois que tu as fais quelques années de Rust, revenir au C++ te paraît tellement archaïque. Tout ces problèmes d'initialisations multiples, d'utilisation d'objets potentiellement déjà détruits et autres subtilités qui devraient être gérées par le compilo plutôt que par le pauvre programmeur sont d'un autre temps; et ajoutent une charge mentale complètement inutile.

    • [^] # Re: C++ oh mon cher vieux C++ ...

      Posté par  (site web personnel, Mastodon) . Évalué à 7.

      Encore heureux pour toi que ce ne soit pas des années d'Ada sinon même ton rustique aurait un autre goût.

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: C++ oh mon cher vieux C++ ...

        Posté par  (site web personnel, Mastodon) . Évalué à 5.

        Le tour de force de Rust c'est d'arriver à faire "presque aussi bien" que Ada de ce point de vue (toutes proportions gardées, mais en comparant au C et C++) mais avec uniquement des vérifications à la compilation et pas à l'exécution.

        Ce qui change pas mal du point de vue des performances.

        C'est aussi vrai qu'on a pas forcément besoin de tant de performances partout. Mais parfois oui.

        On peut donc penser que Rust se trouvera une place confortable quelque part entre Ada (très sécurisé mais moins performant) et C++ (plutôt pas sécurisé, mais possibilités d'optimisations importantes).

    • [^] # Re: C++ oh mon cher vieux C++ ...

      Posté par  . Évalué à 4.

      Bon, c'est vrai, mais il y a aussi tout ce que tu peux faire en C++ et que tu ne peux pas faire en Rust justement parce que le compilateur tient à forcer une seule méthode de gestion des données.

      Par exemple, une liste doublement chaînée, en Rust, sans unsafe, c'est la galère, vu qu'il y a 2 pointeur pour un seul objet. Un structure récursive aussi c'est difficile à implémenter, tu passes un temps fou à te battre avec le compilateur pour qu'il accepte ce que tu lui décris (mais ça c'est grandement amélioré ces dernières années).

      En C++, tu peux te tirer une bombe dans le pied avec une seule ligne de code, mais dès que tu as un peu d'expérience, ça devient bien plus rare et de toute façon, assez facile à débugger. La programmation template, aussi c'est franchement le top (mais c'est imbuvable) pour créer du code assez magique et optimal.

      En Rust, tu as le pouvoir des macros qui franchement manque au C++ avec le pre-processeur tout pourri.

      • [^] # Re: C++ oh mon cher vieux C++ ...

        Posté par  (site web personnel) . Évalué à 7.

        une liste doublement chaînée, en Rust, sans unsafe

        Avec RefCell<T> c'est facile.

        Un structure récursive aussi c'est difficile à implémenter, tu passes un temps fou à te battre avec le compilateur pour qu'il accepte ce que tu lui décris

        Soit tu choisis la facilité avec Box<T>, soit tu gères correctement les lifetimes avec &T, soit tu utilises RefCell<T>, … il existe plein de manière différente de faire ça.

        Ne pas oublier aussi std::mem::replace qui est un très bon ami dans ce genre de situation.

        Avec un peu d'expérience en Rust, et un peu de googling skill, ça devient tout de suite plus facile.

        Je recommande aussi ##rust sur le IRC liberachat. Ils sont actifs, et ont très souvent la réponse a toutes questions que tu pourrais avoir.

        https://link-society.com - https://kubirds.com

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.