Developpez.com

Le Club des Développeurs et IT Pro

CppCon 2016 : Bjarne Stroustrup parle de l'évolution de C++ et s'intéresse à son passé

à son présent mais aussi à son futur

Le 2016-09-23 23:24:55, par Stéphane le calme, Chroniqueur Actualités
Durant l’édition 2016 de la conférence annuelle de la communauté C++ CppCon, le professeur de science informatique danois et créateur du langage Bjarne Stroustrup a tenu à présenter les évolutions du langage, notamment en évoquant son passé, son présent, mais également son futur.

« Je me demandais ce dont j’allais parler durant cette conférence en tant qu’orateur. Puis j’ai regardé le programme et j’ai réalisé qu’il y avait une grosse liste de choses bien détaillées, que ce soit sur le langage, les bibliothèques, etc. Aussi, je me suis dit que je n’allais pas me lancer dans cette voie. J’essaie au contraire de parler de quelque chose de plus général. J’essaie de parler d’évolution. J’essaie de retourner en arrière pour comprendre la raison pour laquelle C++ a connu du succès », a-t-il déclaré en guise d’introduction devant l’auditoire.

Les points sur lesquels Bjarne Stroustrup s'est appesanti sont notamment :
  • le passé : pourquoi est-ce que C++ a connu le succès ?
    • en répondant aux questions avant que les gens ne les posent,
    • en ne suivant pas la masse ;
  • le présent : comment la standardisation façonne-t-elle le C++ ?
    Elle aspire à la stabilité via la compatibilité ;
  • le futur : que devons-nous faire ?
    • concentrer nos efforts à servir la communauté C++ actuelle et à venir,
    • ne pas se dissiper en essayant de plaire à tous ;
  • le futur proche : comment s'y préparer ?
    • trouver des moyens pour utiliser des fonctionnalités des spécifications techniques ISO,
    • développer des lignes directrices.

Durant son intervention, il a donné son avis, par exemple, sur les fonctionnalités d’un bon langage de programmation. Selon lui, bien que tout langage ait besoin de bonnes fonctionnalités fondamentales (par exemple, des mécanismes pour aider à concevoir son code, mais également une bibliothèque standard), un (bon) langage ne saurait se limiter à être un ensemble de bonnes fonctionnalités.

Il s’est également attardé sur les décisions majeures qui ont été prises dans la conception du langage et a indiqué sur une ligne du temps leur impact sur la façon de programmer des développeurs.


Il a expliqué que, pour sa part, les fonctionnalités majeures sont celles qui changent la façon dont les gens pensent le code, tout comme les concepts, qui devraient arriver dans la prochaine version de C++. Il a néanmoins indiqué qu’une combinaison de fonctionnalités mineures peut avoir un impact majeur.

À la question de savoir ce qui fait une bonne extension, il explique que les réponses sont très variées. Du point de vue des développeurs, une bonne extension « aide les gens comme moi et mon projet » :
  • elle résout des problèmes spécifiques ;
  • elle ne nécessite pas de lecture fastidieuses de mode d’emploi ;
  • elle isole le changement ;
  • elle n’entraîne pas de plantage.

Les difficultés rencontrés par les développeurs lors de la conception d’une extension sont :
  • un manque d’expérience dans la planification à long terme ;
  • des objectifs très souvent définis par d’autres personnes ;
  • une évaluation des conséquences sur le court terme.

Du point de vue des concepteurs, une bonne extension « va aider de façon significative la communauté des utilisateurs pendant la prochaine décennie » :
  • elle répond à un problème général / fondamental ;
  • elle fait que le langage est utilisé de façon plus régulière et facilement ;
  • elle améliore la réputation de C++.

Malgré tout, il a reconnu que toutes les extensions font plus ou moins de mal :
  • elles rendent obsolètes certains supports d’apprentissages ;
  • elles peuvent avoir un faible ratio bénéfice / coût ;
  • elles pourraient ne venir en aide qu’à une portion de la communauté ;
  • elles pourraient induire des coûts à des personnes qui ne les utilisent pas ;
  • elles pourraient être une entrave pour les efforts de pratique de programmation.


Tous ces critères ont aidé à façonner C++17. Ainsi, des modifications dont les effets sont relativement isolés ont été acceptées sans problème, comme de simples variables globales, c'est-à-dire la syntaxe inline int x = f();. Au contraire, la convention d'appel unifiée a été rejetée : elle proposait que l'écriture f(x) soit équivalente à x.f() dans tous les cas.

Dans son intervention, il évoque de nombreux points intéressants relatifs à l'évolution de C++.


Source : YouTube

Voir aussi :

CppCon : Bjarne Stroustrup annonce le projet C++ Core Guidelines, pour aider les développeurs à utiliser le C++ moderne de façon plus efficace
  Discussion forum
24 commentaires
  • JolyLoic
    Rédacteur/Modérateur
    As tu déjà essayé auto sur des vrais projets ? Moi, oui, et je n'ai pas rencontré les problèmes dont tu parles.
    Au contraire, je trouve le code plus lisible avec auto que sans. Parce qu'il permet de s'éloigner du type d'une variable, pour se concentrer sur son rôle. Et que c'est ça qui compte le plus.

    Envoyé par mister3957

    Quand ça parle de plusieurs centaines de milliers de lignes de code, fait en équipe, via des bibliothèques partagées (statiques ou dynamiques) entre plusieurs centres de services, qu'est-ce que tu en sais que v.front() est de type "std::string" puisqu'il est de type "auto" ?
    Je n'en sais rien, et je m'en moque. Ce qui compte c'est de savoir que v contient des chaînes, et quelles chaînes (est-ce que ce sont des noms, des titres, des phrases...). Savoir si ce sont des std::string, des QString, des glib::ustring... n'importe pas vraiment. C'est similaire du fameux "program to interface, not implementation" des langages comme C# ou Java.

    Envoyé par mister3957

    Si à l'autre bout du monde on change le type car auto le promet que ça se fera sans encombre, qu'est-ce qui te garantie que le .c_str() fonctionnera ?
    Personne (et auto n'a jamais promis qu'on pouvait changer de type de manière 100% transparente...). Dans certains cas ça marchera, dans d'autres cas, il faudra apporter des modifications fastidieuses au code suite au refactoring. En pratique, il y a quand même pas mal de cas où ça marche, les templates ont bien formé les gens à utiliser des interfaces identiques. Mais sans auto, c'est garanti que ça ne marcherait jamais, voire pire, que ça marcherait en ajoutant silencieusement des opérations inutiles (conversion d'une chaîne en une autre, par exemple).

    Envoyé par mister3957

    Perso je préfère avoir le type sous les yeux, à commencer par le préfixe de mes variables et de celles que j'expose en paramètre, et le "const compliant", quitte à les changer quant le type change. Certes ça demande du taf de singe, mais au moins on sait ce que l'on manipule sur le moment et pas besoin de remonter à la source (qui peut être très lointaine dans le code) afin de deviner pour déduire les choses, d'en présupposer d'autres ou de se reposer sur les erreurs / warnings du compilateur pour me laisser guider.
    Si la source des variables est loin dans le code, il y a un autre problème. J'ai très rarement plus de 20 lignes entre le moment où une variable est initialisée, et le moment où elle est utilisée pour la dernière fois (c'est souvent moins de 10). Et voir comment la variable est initialisée est suffisant pour savoir quoi en faire, quand les noms de variables/fonctions sont bien choisis. Et s'ils ne sont pas bien choisis, ce n'est pas de connaître le type qui va aider.

    Envoyé par mister3957

    "auto str", c'est bien joli mais qu'est-ce que je peux en faire de ce "str" si je ne sais pas ce que sais ?
    auto str, tu ne peux rien en faire, et le compilateur non plus, on est bien d'accord. auto str = f();, je suis aussi d'accord qu'on ne peut pas en faire grand-chose non plus. Mais franchement, std::string str = f();, je saurais lui faire faire des choses, certes, mais des choses utiles ? Non. Savoir que str est une chaîne ne m'aide absolument pas à savoir ce qu'elle contient et ce qu'il faudrait que j'en fasse. auto searchCriterion = mySearchDialog.getResult();, là, oui, je commence à bien savoir qu'en faire. Et la plupart du temps, je n'ai pas besoin du tout de savoir le type exact pour manipuler mon seachCriterion. Je peux le placer dans une searchHistory, le transmettre à un searchEngine et demander à ce dernier des searchResults... Et je suis certain que tu as compris en quoi consistaient les opérations dont je parlais, alors même que je n'ai pas précisé si searchCriterion était un std::string, un QString, une std::regex, une classe (avec une string, un bool caseSensitive, et une enum normal/regex/wildcards)...

    J'aurais pu écrire ce code : SearchCriterion searchCriterion = mySearchDialog.getResult();, là, il n'y a plus d'auto, mais je ne trouve pas que le code gagne en lisibilité. J'aurais aussi pu l'écrire (en supposant que je me lie au fait qu'actuellement, un searchCriterion est juste une chaîne, et que je devrais revenir là-dessus si je désire enrichir mes possibilités de recherche : std::string searchCriterion = mySearchDialog.getResult();. Est-ce pour autant que j'ai vraiment appris plus de choses sur searchCriterion ?
    On pourrait me dire, "oui, tu sais que c'est une std::string, donc par exemple, tu sais que tu as le droit d'écrire if(!searchCriterion.empty()) searchCriterion[0] = 'a';". Sauf qu'en fait, c'est faux. On n'a pas le droit d'écrire ça, car c'est une chaîne de recherche, et dans mon cas, une recherche peut avoir une syntaxe spéciale, validée par mySearchDialog.getResult();, et que modifier un caractère comme ça à la hussarde peut rompre les invariants de mon code. Les invariants ne sont pas tous dans le système de type, mais aussi dans le contrat des fonctions. Ils sont sémantiques, pas syntaxiques. Ils sont donnés par les diverses fonctions qu'on appelle, celles-là même qui sont le point d'attention majeur quand on utilise auto. Et qu'utiliser auto permet justement de se focaliser sur la sémantique, en réduisant le bruit lié à la syntaxe.
  • Gugelhupf
    Modérateur
    Envoyé par mister3957
    Un truc qui m'horripile du C++11 c'est le concept d'"auto" avec pour objectif de pouvoir facilement changer le type des choses sans devoir repasser partout dans le code. Mais du coup, à manipuler ce code, on ne sait plus ce que l'on manipule. J'ai pas bien envie que C++ devienne JS ou PHP, ça le dénaturerait.
    En JS, PHP, Python, le typage est implicite, tu peux ne pas savoir ce que tu manipules si l'initialisation/affectation se fait hors scope, mais en C++ avec auto ou en C# avec var (qui n'a rien avoir avec celui du JS) c'est juste du sucre syntaxique, tu appliques l'inférence de type, ton IDE peut facilement t'aider à retrouver ton type si tu pointes ton curseur sur le nom de ta variable, tu sais ce que tu manipules. La preuve que l'inférence de type est différent des langages de script : elle ne s'applique pas aux paramètres de fonction
  • Pyramidev
    Expert éminent
    Envoyé par mister3957
    Un truc qui m'horripile du C++11 c'est le concept d'"auto" avec pour objectif de pouvoir facilement changer le type des choses sans devoir repasser partout dans le code. Mais du coup, à manipuler ce code, on ne sait plus ce que l'on manipule. J'ai pas bien envie que C++ devienne JS ou PHP, ça le dénaturerait.
    auto a plusieurs buts. Personnellement, au quotidien, les moments où j'ai le plus envie d'utiliser auto, c'est quand j'appelle une fonction de la STL dont le type de retour est connu, mais fait 3 km.

    Exemple :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // Sans auto :
    {
        std::pair<std::map<IdUtilisateur, Utilisateur>::iterator, bool> tmp = mapUtilisateurs.insert(std::make_pair(id, utilisateur));
        bool identifiantPasDejaUtilise = tmp.second;
        // ...
    }
        
    // Avec auto :
    {
        auto tmp = mapUtilisateurs.insert(std::make_pair(id, utilisateur));
        bool identifiantPasDejaUtilise = tmp.second;
        // ...
    }
        
    // Avec les structured bindings de C++17 :
    {
        auto [it, identifiantPasDejaUtilise] = mapUtilisateurs.insert(std::pair(id, utilisateur));
        // ...
    }
  • JolyLoic
    Rédacteur/Modérateur
    Envoyé par Pyramidev
    S'il y a des invariants à respecter, je pense qu'il est préférable de créer une classe à part, même si elle est minimaliste
    Tu fais donc une classe pour les doubles positifs quand tu veux appeler sqrt, une autre pour les doubles strictements positifs quand tu veux appeler log, une autre pour les chaînes de caractères non vides, une autre pour...
    J'ai essayé à un moment sur du vrai code, mais j'ai trouvé ça bien trop lourd et finalement peu utile dès que tu es lié à des bibliothèques externes qui elles n'ont pas été écrites avec tes invariants en tête. Peut-être que si je devais coder dans un domaine où des vies humaines dépendent de mon code, je regarderai à nouveau ça de plus près, même si dans ces domaines, c'est encore la preuve de code (qui n'utilise pas le typage, mais des informations sémantiques) qui semble le plus utilisé...

    Envoyé par Pyramidev
    Quand je lis SearchCriterion searchCriterion = mySearchDialog.getResult();, si je veux en savoir plus sur SearchCriterion, je vais lire "SearchCriterion.h". Donc, oui, je pense que le code gagne en lisibilité.
    À partir du moment où tu parles de naviguer dans le code, on ne parle plus de la lisibilité passive, mais d'une lisibilité active. Avec mon IDE, que j'utilise auto ou pas, aller à la définition d'un type, c'est 1 touche à appuyer. Je pense que c'est plus efficace que toute stratégie qui serait basée sur une homonymie espérée entre nom de type et nom de fichier... Je ne suis donc pas très convaincu par cet argument...
  • jo_link_noir
    Membre expert
    Envoyé par foetus
    Je ne pense pas que ce soit pareil
    C'est exactement ce qu'il dit: c'est différent.
    Je pense que le reproche mister3957 vient dans la relecture d'un code qui n'utilise que auto: on perd l'information de type à la lecture. Personnellement, je trouve cette information peu utile puisqu'il reste le nom de la variable, la valeur d'initialisation et le contexte d'utilisation.

    Envoyé par foetus
    Mais avec les templates , tu as une notion de contrat.
    Pas plus qu'avec auto. Il y a certes une information en plus qui est le nom du type, mais cela reste un contrat implicite qui se vérifie au moment où de l'utilisation. Pas d'opérateur + ? Erreur sur la ligne qui l'utilise. Une notion de contrat devrait être vérifiée au moment de l'initialisation, comme ce que proposent les concepts.

    Si j'ai bien compris les quelques trucs vu sur les concepts, ceux-ci pourront remplacer une bonne partie des types templates et auto, tout en offrant une vérification d'interface implicite via un contrat nommé.
    Il y aussi une généralisation de auto jusqu'au paramètre de template pour les paramètres qui n'ont pas de contrat défini.
  • Ehonn
    Membre chevronné
    Envoyé par mister3957
    Donc ça apporte de la "magie" qui nécessite obligatoirement de la lecture de docs (et donc du temps) pour la comprendre afin pouvoir de la manipuler, juste pour savoir ce que l'on manipule.
    La première fois que tu vois un nouveau type, il faut aller voir la documentation pour savoir comment l'utiliser / le manipuler (auto ou pas).
    Maintenant, il faut soit aller voir la documentation, soit demander à son compilateur, pour connaître le type.

    Envoyé par mister3957
    Ça pue la régression cette histoire
    Non, car on "utilisait" déjà auto avant (sous une autre forme) :
    Code :
    std::vector<iterator>::reverse_iterator it = v.rbegin();
    Code :
    std::vector<bool>::size_type const n = v.size();
    Probablement le meilleur exemple :
    Code :
    typedef boost::result_of<functor(int)>::type r = functor(7);
    auto est très justifié pour les les cas où, au final, le type de retour n'est pas important (contrairement aux services) (le nom de la fonction et/ou de la variable suffit).
    Dans l'exemple de Pyramidev, un std::pair est utilisé mais l'utilisation d'un std::tuple, d'un boost::optional ou de n'importe qu'elle classe qui offrent les même services (ou équivalents) est légitime.
  • zobal
    Membre confirmé
    Envoyé par mister3957
    Donc ça apporte de la "magie" qui nécessite obligatoirement de la lecture de docs (et donc du temps) pour la comprendre afin pouvoir la manipuler, juste pour savoir ce que l'on manipule.

    Ça pue la régression cette histoire
    Si c'est une blague, ça commence à être lourd.
    Sinon, comme dit précédemment, le type est celui de la valeur d'initialisation.

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int a = 4;
    auto x = a;           // x est de type int
    
    float b = 3;
    auto y = b;           // y est de type float
    
    vector<string> v {"texte1", "texte2"};
    auto tab = v;             // tab est de type vector<string>
    auto str = v.front();     // str est de type string
    auto iter = v.begin();    // iter est de type vector<string>::iterator
    
    auto f = [] (int n) { return n > 0; };  // f est de type function<bool(int)>
    Oulala que c'est magique.
  • JolyLoic
    Rédacteur/Modérateur
    La plupart du temps, j'essaye de trouver un nom qui définisse le rôle de la variable (password, plutôt que passwordString ou str). Mais il y a des cas où je n'y arrive pas (essayez de trouver un nom descriptif pour un itérateur!). Sans ce cas là, il y a homonymie non pas variable/type, mais plus variable/concept auquel répond le type.

    Pour reprendre le cas de l'itérateur, je le nommerais plutôt it ou iterator que vector_of_string_iterator ou encore je nommerais une variable fileList même si son type est std::vector<std::string>.

    D'ailleurs, à ce sujet, il y a eu des suggestions pour étendre les concepts à la déclaration de variables, histoire d'avoir un intermédiaire entre le type, trop précis, et auto, perçu par certains comme trop imprécis. On pourrait alors écrire Iterator it = v.begin();, le type de it serait déduit comme avec auto, mais ensuite, on vérifierait si ce type répond au concept Iterator, et si ce n'est pas le cas, on lèverait une erreur. Je ne sais pas trop si j'utiliserais une telle écriture si elle devenait disponible, ou si je resterais avec auto. Il faudrait que je l'expérimente dans la vraie vie.
  • Bousk
    Rédacteur/Modérateur
    Envoyé par mister3957
    ...

    Pour l'alignement mémoire, c'est pareil. Est-ce que je dois faire

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A
    {
    public:
       ....
    private:
       auto titi A;
       auto tata B;
    }
    ou

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A
    {
    public:
       ....
    private:
       auto tata B;
       auto titi A;
    }
    ...
    Mais auto n'est pas utilisable dans ce cas. Il n'a strictement rien à voir avec quelconque problème de performance, conso, poids du livrable, quelque soit le niveau ni rien. Ce n'est qu'un sucre syntaxique.
  • Ehonn
    Membre chevronné
    Je n'ai rien contre toi (je préfère le préciser ) mais j'ai une question sur un point spécifique :

    À quel moment typedef boost::result_of<functor(int)>::type n'est pas aussi "magique" qu'auto ?

    PS : @zobal
    Note que vector<string>::iterator n'est pas un type vraiment/entièrement définit par la norme, on sait juste que ça respecte le "concept" de random access iterator (?)
    Idem, pour les lambdas, le type de retour n'est pas définit (mais convertissible vers std::function).