IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Pointeurs intelligents vs. pointeurs bruts


Sujet :

C++

  1. #41
    Membre habitué
    Inscrit en
    Octobre 2010
    Messages
    64
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 64
    Points : 146
    Points
    146
    Par défaut
    Citation Envoyé par Joel F Voir le message
    Personnelement, je me débrouille toujours pour faire en sorte que sur bad_alloc, l'exception soit catché, le pointeur renvoyé devient NULL et la prochaine fonction utilisant ce poineur ASSERT sur la précondition ptr != NULL.
    Tu le fais systematiquement?
    Ca ne rend pas ton code indigeste?

  2. #42
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    Avec l'age j'ai un petit pool de fonction/classe qui gère ma mémoire de manière sécurisée dont le catch + rethrow est centralisé.

    Ensuite:

    ASSERT( ptr && "NULL pointer passed as argument");

    ne prend pas 16567 lignes non plus.

  3. #43
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    Salut koala,

    j'ai argumenté que les références introduisent une ambiguïté par rapport au C. En effet, en C,

    x.y

    veut dire que x est une structure (la structure elle-même). En C++, il y a deux options: soit x est un objet, soit une référence. Deux sémantiques, une seule syntaxe, donc, ambiguïté. Je ne vois pas comment on peut le nier. Tu me réponds que la syntaxe est identique... Mais c'est justement parce que la syntaxe est identique qu'il y a ambiguïté.
    Ce que tu dois comprendre, c'est qu'un pointeur est un type de variable particulier en cela qu'il contient... l'adresse mémoire à laquelle se trouve l'objet pointé, alors qu'une référence n'est qu'un alias, un autre nom, pour la variable d'origine.

    De ce fait, je vais dire que tu t'en fous royalement du fait que tu travaille ou non avec une référence: cela s'applique, au final, exactement sur le même objet.

    Il n'y a strictement aucune raison de faire une différence entre une référence et l'objet d'origine, simplement, parce que c'est la même chose
    En effet, c'est plus léger syntaxiquement. C'est un avantage indéniable des références. Et pourtant, reste que c'est une duplication de fonctionalité et que cela introduit une ambiguïté syntaxique (unique au C++ d'ailleurs).
    cf plus haut

    Je suis au courant de ce que tu mentionnes au sujet des constructeurs, de new, de this, etc. Je ne faisais qu'illustrer que si les références étaient réellement sensées remplacer les pointeurs "dans la mesure du possible", elles auraient été implémentées différemment. "this" aurait été une référence, "new" aurait retourné une référence, la librairie standard C++ n'utiliserait que des références, la librairie standard C serait entièrement réimplémentée en termes de références, etc.
    Ce que tu ne comprend pas, c'est qu'une référence remplace, effectivement, avantageusement les pointeurs, entre autres, quand l'objet référencé n'a strictement aucun sens à être nul.

    Tu ne peux absolument pas donner une telle garantie avec un pointeur, alors qu'avec une référence, elle est implicite et non contournable.

    De plus, new (et delete) intervienne "par nature" dans la gestion dynamique de la mémoire, or, une référence n'a strictement aucun rapport avec celle-ci.

    Sans oublier le fait que les objet créés sans avoir recour à l'allocation dynamique sont détruits automatiquement lorsque l'on sort de la portée dans laquelle ils sont déclarés.

    On ne peut donc pas faire en sorte que new renvoie une référence, simplement parce qu'il renverrait... une référence sur un objet invalidé du fait de sa destruction automatique.

    Enfin, si C++ est à considérer comme un langage totalement séparé de C, il revendique néanmoins son héritage par rapport au C.

    Si on avait du réimplémenté la bibliothèque C standard pour qu'elle manipule des références, il y aurait eu d'une part beaucoup plus d'incompatibilités entre C et C++ et, d'autre part, on aurait du réinventé complètement la roue


    Vu que 1) la seule utilisation obligatoire des références dans le langage est la définition des surcharges d'opérateurs et des constructeurs de copie, et
    C'est pourtant loin d'être les seuls cas où les références sont utilisées.

    Regarde l'ensemble de la STL, et tu remarquera qu'elles sont utilisées quasiment PARTOUT, que ce soit sur des fonctions libres ou sur des fonctions membres de classes

    2) les contraintes d'utilisations les rendent inutilisables dans bien des cas (pas de réassignation et pas de nullité), ça m'apparaît plutôt un ajout tardif et sans grande importance. Je suis plutôt d'avis que les pointeurs restent la façon standard de "référencer".
    Tu ne peux, effectivement, pas faire en sorte qu'une référence fasse référence à un autre objet, mais n'oublie pas que l'opérateur d'affectation ( = ) renvoie... une référence sur l'objet d'origine modifié

    Et la non nullité des références, et, justement, une sécurité que tu ne peux pas obtenir avec un pointeur.

    Comme je dit précédemment, c'est un apport non négligeable parce que tu n'as, justement, pas besoin de tester ton pointeur.

    Cela permet d'un coté d'éviter que les "incompétents" oublient de tester la non nullité du pointeur, et, pour les autres, d'éviter des tests inutiles, et donc des pertes de performances dans certaines situations.

    Que demander de plus

    Il ne faut pas chercher bien loin pourtant. Par exemple, les streams standards utilisent les pointeurs pour les fonctions read et write. Pas moyen de passer une string& ou quoique ce soit d'autre.

    istream& read ( char* s, streamsize n );
    ostream& write ( const char* s , streamsize n );
    Tu semble oublier allègrement les opérateur de flux << et >>, qui travaillent sur les référence, et l'ensemble des autres classes (conteneurs en tête) qui manipulent essentiellement des références
    Ou encore, la STL est composée de trois choses: des conteneurs, des algorithmes et des itérateurs. Or les itérateurs sont implémentées comme des pointeurs: ils peuvent être déréférencés, il peuvent être incrémentés, et ils peuvent pointer sur "rien" (sémantiquement).
    Les itérateurs ont, effectivement, sémantique de pointeur, simplement parce qu'il faut bien faire la différence entre ce qui est de la responsabilité de l'itérateur (la capacité à passer à l'élément suivant) et ce qui est de la responsabilité de l'objet itéré.

    Comme, de plus, les itérateurs sont prévu pour fonctionner dans l'intervalle semi ouvert [begin ; end [, il fallait bien leur donner la possibilité de représenter... un élément invalide
    Donc, les pointeurs font partie intégrante de la librairie standard C++.
    Bien sur que les pointeurs font partie intégrante du langage et de la bibliothèque standard.

    Ne serait-ce que parce qu'autrement, tu n'aurais pas la facilité actuelle d'interfaçage du C avec C++.

    Mais les références font, aussi, partie intégrante du langage et de la bibliothèque standard.

    Et, comme les références ont des cas d'utilisation identiques aux pointeurs, mais que les pointeurs présentent malgré tout des cas d'utilisation que les références ne présentent pas, le mieux est encore d'utiliser les références "chaque fois que possible" et les pointeurs "lorsque l'on n'a pas le choix"

    De cette manière, tu sécurise à peu de frais toute une plage de situations dans laquelle tu aurais du sécuriser toi-même l'utilisation des pointeurs.
    Et ça ne marche qu'avec une référence constante, donc l'objet sera non-modifiable. Et si je veux le modifier? J'en reviens encore à utiliser un pointeur.
    A quoi te servirait de vouloir modifier une variable anonyme temporaire

    Quel intérêt aurais tu, avec une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo(std::string const & str)
    {
        /* tu fais ce que tu veux de str */
    }
    quand elle est appelée sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int main()
    {
        foo("bonjour");
    }
    De plus, comme je l'ai dit, le concept de const-correctness t'apporte, dés la compilation, la certitude que tu ne modifie pas des choses que tu ne peux pas ou que tu ne veux pas modifier.

    Il serait dommage de se passer de cette possibilité

    Si, pour une raison ou une autre, tu veux, effectivement, pouvoir modifier un objet issu de la fonction appelante dans la fonction appelée, tu reste tout à fait libre d'utiliser une référence non constante, mais, il n'y a alors aucun sens à vouloir utiliser une variable anonyme temporaire...
    De toute façon, ce n'est qu'un raccourci syntaxique; l'objet en question doit bien exister quelque part pour être passé en paramètre, et il va en fait être sur la pile comme tous les autres.
    Oui, mais tu as la certitude qu'il existe... Certitude que tu ne peux obtenir que par un test lorsque tu manipule un pointeur
    Donc, je te concède encore une fois que les références permettent des raccourcis syntaxiques intéressants dans certains cas.
    Et surtout d'apporter une sécurisation largement supérieure à celle que tu obtient "par défaut" (sans intervention de l'utilisateur) avec les pointeurs.

    Et comme cette sécurisation te permet d'éviter les tests d'existence que tu es tenu de mettre en place avec les pointeurs, tu gagne "sur les deux tableaux" que sont les performances et la sécurité...
    Mais dans mon expérience, ces cas sont loin de former la majorité et il arrive si souvent qu'on doivent changer une référence pour un pointeur que l'on est mieux de toujours utiliser des pointeurs, simplifiant la syntaxe (plus lourde oui mais plus cohérente et explicite) et d'éviter ainsi de perdre du temps.
    On a, alors, des expériences tout à fait différentes...

    Parce que les cas dans lesquels j'ai du décider de remplacer une référence par un pointeurs sont particulièrement rares.

    Et, attention, je ne dis pas qu'il faut commencer par coder sous la forme d'une référence, et décider, parce que l'on n'a pas d'autre choix, de remplacer la référence par un pointeur, mais je dis qu'il faut, dés le départ, réfléchir à ce que l'on fait, et utiliser une référence "chaque fois que possible" et un pointeur "quand on n'a pas le choix"...

    C'est quand même différent
    Or le concept d'une "référence qui ne peut pas être nulle" n'existe pas dans la plupart des langages: Java, C#, Python, etc. Et pourtant on peut garantir qu'une fonction ne retournera pas nul dans ces langages: en lançant une exception en cas d'erreur.
    C'est parce que ces langages travaillent de manière tout à fait différente...

    Java et C# utilisent un système "externe" de gestion de la durée de vie des objets, et tu ne peux pas décider, comme en C++, de prendre la resonsabilité de cette gestion ou non.
    La même chose est possible en C++, et il n'y a donc pas besoin d'une "référence qui ne peut pas être nulle". Je peux retourner un pointeur si tout va bien, et lancer une exception en cas d'erreur.
    Et cela ferait double emploi...

    Les pointeurs présentent, effectivement, l'intérêt majeur de pouvoir être nuls.

    Cela signifie que, si tu as une fonction qui renvoie un pointeur, tu va, naturellement, envisager un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void foo()
    {
        Type * ptr = bar();
        if(ptr != NULL)
        {
            /* ce qu'il faut faire */
        }
    }
    simplement parce que, en bon programmeur C, tu auras pris l'habitude de vérfier qu'un pointeur récupéré par ailleurs n'est pas nul, alors que ce code n'a aucun intérêt si bar lance une exception en cas de nullité du pointeur
    Ma fonction garantit donc que le pointeur sera non-nul. Le fait d'utiliser une référence ici est sans aucune utilité! Si j'utilise une référence, le code de la fonction sera le même à 2 caractères près (&*), et la sémantique de la fonction sera exactement la même.
    Là n'est pas le problème...

    Par contre, le fait que tu doive faire une différence dans ton code parce que tu manipules un pointeur peut parfaitement en être un, alors que, justement, tu garde la possibilité d'utiliser une référence comme s'il s'agissait de l'objet d'origine, et que c'est ce qui arrive réellement: les actions que tu effectue au départ d'une référence sont, en réalité, effectuées au départ de l'objet référencé.
    En fait nous sommes en désaccord fondamental ici.
    Là, pour une fois, je suis tout à fait d'accord avec toi
    C++ sert les mêmes buts que le C, à plus grande échelle. Il est essentiellement implémenté comme un superset du C.
    Là, par contre, je suis, effectivement en total désaccord avec toi.

    C et C++ sont deux langage totalement différents et, bien que l'on retrouve dans C une grosse partie de ce que l'on trouve en C, ce n'est pas une raison pour développer en C++ en utilisant les possibilités issues du C, essentiellement parce qu'il existe en C++ des possibilités similaires qui apportent une sécurisation largement plus importante.

    Il est déjà "suffisamment facile" de se "tirer une balle dans le pied" en C++ pour que l'on tente d'éviter les problèmes que l'on peut éviter en préférant les possibilités issues du C++
    C++ est un langage de gestion manuelle de mémoire et la syntaxe des pointeurs du C est parfaitement adaptée à cette tâche.
    Si ce n'est que C est un langage exclusivement séquentiel, alors que C++ est un langage multi paradigme (séquentiel, certes, mais aussi objets et générique).

    Les deux paradigmes supplémentaires nous permettent de travailler mieux et de manière plus sécurisante, même lorsque l'on travaille en séquentiel, il serait donc dommage de se priver de ces possibilités, même s'il est, effectivement, possible de le faire.
    (D'ailleurs C# l'a repris pour le code "unsafe".) Les références ne servent qu'à permettre certains raccourcis syntaxiques et à donner l'impression que C++ est plus orienté-objet qu'il ne l'est, au prix d'une duplication de fonctionnalités, de l'introduction d'une ambiguïté syntaxique, et de discussions comme celle-ci.
    Non...

    C++ est réellement orienté objets. Il n'est, effectivement, pas exclusivement orienté objet, mais c'est, quelque part, ce qui fait sa force comparé à des langages qui ne le sont absolument pas (C++ en tête) ou qui le sont à outrance (java en tête).

    Nous sommes d'accord. Je ne fais pas de différence entre "un cas d'exception" et "une erreur". Une exception peut être utilisée partout où on veut retourner une erreur.
    Tu semblais pourtant dire le contraire
    Parfois, cependant, on peut très bien vouloir retourner nul sans que cela représente une erreur. Par exemple, une fonction de recherche ne devrait pas lancer d'exception si elle ne trouve pas l'objet voulu, mais bien retourner nul. C'est ce que fait std::find d'ailleurs; mais comme std::find retourne une référence, il se voit contraint de retourner une référence sur un "objet invalide" (l'itérateur end() ou npos), et là on nage en plein ridicule, puisqu'un pointeur ferait exactement la même chose de façon beaucoup plus simple.
    Un pointeur ferait, effectivement, exactement la même chose, mais la simplicité serait apportée au concepteur, alors que le fait de renvoyer end complexifie, peut être, la tâche au concepteur, mais facilite celle de l'utilisateur.

    En effet, toutes les collections de la STL présente une interface commune, composée des fonctions membres begin et end.

    Cela signifie que tu peux respecter beaucoup plus facilement OCP (Open Close Principle: le fait qu'un code doit être ouvert à l'évolution et fermé à l'exécution), simplement parce que, avec un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo()
    {
        for_each(truc.begin(),truc.end(), functor());
    }
    tu pourra décider à n'importe quel moment, et pour des raisons variées, de remplacer ton std::vector par n'importe quelle autre collection "compatible".

    La notion d'itérateur, et les fonctions membres associées, est, simplement, une notion supplémentaire (même si elle s'utilise comme un pointeur) qui permet de faire en sorte que, pour autant que tu garde une interface compatible, tu puisse ne pas t'inquiéter de la collection qui sera réellement manipulée.

    Et cela rentre en réalité non pas dans le domaine de la programmation objets, mais bel et bien dans celui de la programmation générique
    Je suis entièrement d'accord, et comme tu comprends cette différence tu as de bonnes chances d'utiliser correctement les pointeurs intelligents. Mais j'ai trop souvent entendu dire qu'ils permettaient d'abstraire la gestion de mémoire comme en Java, alors que c'est tout à fait faux. C'est un outil qui aide dans certains cas mais qui n'offre aucunement les caractéristiques de performance et de sécurité de Java. En C++ la seule entité qui peut gérer correctement la mémoire, c'est le programmeur, et s'il ne comprend pas comment les outils qu'il utilise fonctionnent, c'est le désastre assuré.
    Attention, je suis le premier à dire que C et C++ sont des langages particulièrement complexes, avec lesquels il est des plus simples de se tirer une balle dans le pied.

    Et je suis d'accord avec toi qu'on ne peut pas laisser qui que ce soit croire que les pointeurs intelligents présentés par C++ permettent ce que le Garbage Collector permet en java ou en C#

    Citation Envoyé par el_socio Voir le message
    C'est la remarque que j'attendais, et pour laquelle j'avais prepare le terrain avec mon histoire sur dev type "ingenieur" vs type "universitaire" (avec tous les guillemets que vous voudrez, car les deux sont intimements interdependants et je ne concois pas l'un sans l'autre).
    En fait, si un new echoue (sur quelque chose qui ne demande rien d'autre que de la memoire), alors:
    1. Je ne vois pas l'interet de gerer cette exception. Si ce new echoue, on ne pourra meme pas allouer une chaine de carractere pour faire un log, et meme un log sans fichier (un log en reseau par exempl).
    2. Aujourd'hui, et dans mon environnement de dev (c'est a dire des programmes destines a fonctionner sur des desktops ou des serveurs) il y a statistiquement moins de chance qu'un new echoue qu'une surtension face griller le disque dur.
    Absolument pas...

    D'abord, comme l'a fait valoir Joel, tu as certains systèmes qui fournissent un log "matériel", mais, surtout, il faut comprendre que new[] essaye d'allouer de l'espace contigu en mémoire.

    Or, ce n'est pas parce que le système est incapable de trouver XXX bytes contigus en mémoire qu'il sera d'office incapable de trouver XXX-1 bytes

    De plus, les chaines de caractères std::string n'utilisent pas forcément un espace contigu de mémoire pour représenter l'ensemble des caractères qui les composent.

    Seule sa fonction membre c_str() présente ce type de limitation (étant donné qu'elle renvoie, effectivement, une chaine C style)

    Il se peut, effectivement, que tu n'ai pas la possibilité d'allouer la mémoire nécessaire à la représentation interne de ta chaine, mais c'est loin d'être forcément le cas
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #44
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    reccord du post le plus long imo

  5. #45
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par koala01 Voir le message
    De plus, les chaines de caractères std::string n'utilisent pas forcément un espace contigu de mémoire pour représenter l'ensemble des caractères qui les composent.
    En pratique, si, et dans la prochaine version du standard, cette pratique sera rendue obligatoire.

    Le truc classique pour logger en cas de problème d'allocation mémoire est de pré-allouer une petite zone au début du programme qui servira uniquement dans ce cas.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  6. #46
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Citation Envoyé par el_socio Voir le message
    C'est pourquoi je ne sais pas si, dans le cas precis ou on programme une bibliotheque destinee a etre utilisee par d'autres developpeurs, l'utilisation de pointeurs intelligents doit-elle etre systematique pour l'interface publique des classes que l'on met a disposition des utilisateurs?
    Certainement pas. Les librairies standard C++, par exemple, n'exposent que des pointeurs bruts (i.e. string::c_str()). Les pointeurs intelligents pourraient être utiles dans l'implémentation de cette librairie, et peut-être peuvent-ils utiles dans l'interface publique, mais je n'ai pas encore rencontré ce cas.

    Je vais essayer de répondre à koala maintenant en faisant un peu plus court que la dernière fois.

    Premièrement, la fameuse "garantie" de non-nullité des références est une chimère, puisqu'une référence peut très bien être invalide. Tous les débutants font l'erreur suivante:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    string& MyFunction()
    {
    	string a("Hello!");
    	return a;
    }
    Tu me diras peut-être que le compilateur devrait émettre un warning, mais il émettrait le même warning si un pointeur avait été utilisé à la place. Ou encore, si je prends une référence sur un objet alloué sur le tas, et qu'il est détruit, ma référence est encore une fois invalide. Donc, une référence n'apporte aucune garantie réelle par rapport à un pointeur. Et c'est encore pire parce que je n'ai aucune façon de tester la validité d'une référence, tandis qu'un pointeur sera typiquement nul.

    Un pointeur ferait, effectivement, exactement la même chose, mais la simplicité serait apportée au concepteur, alors que le fait de renvoyer end complexifie, peut être, la tâche au concepteur, mais facilite celle de l'utilisateur.

    En effet, toutes les collections de la STL présente une interface commune, composée des fonctions membres begin et end.
    Mais end() pourrait aussi bien être nullptr et cela pourrait constituer l'interface commune de la STL. Ce serait plus simple puisque cela utiliserait une fonctionnalité du langage plutôt que de requérir une nouvelle fonction de librairie. Dans tous les langages orientés-objets et en C, une fonction de recherche qui ne trouve rien retourne un "pointeur nul". Cela n'empêche pas d'avoir des interfaces communes.

    Le fait que std::find retourne end(), ou string::npos dans le cas d'une string, est une complexité supplémentaire non seulement pour le concepteur mais aussi pour l'utilisateur de la librairie, qui doit apprendre une fonction et un objet statique supplémentaire plutôt que d'utiliser la fonctionnalité du langage qui devrait intuitivement servir à cette tâche. Encore une fois, end() et npos ne font qu'émuler le pointeur nul: c'est sémantiquement la même chose! Or ré-implémenter dans une librairie ce qui est déjà offert par le langage, c'est nager en plein absurde.

    Ce que tu dois comprendre, c'est qu'un pointeur est un type de variable particulier en cela qu'il contient... l'adresse mémoire à laquelle se trouve l'objet pointé, alors qu'une référence n'est qu'un alias, un autre nom, pour la variable d'origine.

    De ce fait, je vais dire que tu t'en fous royalement du fait que tu travaille ou non avec une référence: cela s'applique, au final, exactement sur le même objet.

    Il n'y a strictement aucune raison de faire une différence entre une référence et l'objet d'origine, simplement, parce que c'est la même chose.
    Je comprends très bien ce que sont les pointeurs et les références, merci.

    Il y a deux bonnes raisons de vouloir différencier entre un objet et une référence en C++. La première est qu'une référence peut être invalide, comme je l'ai illustré plus haut, alors qu'un objet, non. La deuxième est que la syntaxe générale pour "référencer" en C++, celle qui s'applique dans 100% dans des cas, est différente, et qu'on introduit donc une confusion (une ambiguïté) en utilisant une autre syntaxe pour représenter la même chose.

    Tu ne peux absolument pas donner une telle garantie avec un pointeur, alors qu'avec une référence, elle est implicite et non contournable.
    Non! J'ai illustré ici qu'une référence peut être invalide (ce qui revient au même), et j'ai illustré dans mon message précédent qu'on peut garantir qu'un pointeur sera non-nul. Donc, tout ce que tu gagnes avec une référence, c'est une certaine légèreté syntaxique dans certains cas.

    Tu sembles impliquer que tout pointeur doit toujours être testé pour savoir s'il est non-nul. Or, en Java par exemple (et ça n'a rien à voir avec le fait que la mémoire est gérée dans ce langage), toute référence peut être nulle en tout temps. Et pourtant, on ne passe pas son temps à écrire if (untel == null). Et on arrive à écrire des programmes qui marchent très bien quand même.

    Et en fait, il n'existe aucun langage, autre que C++, avec un concept de "référence qui ne peut pas être nulle". Et pourtant, on ne passe pas son temps à tester la nullité des références dans ces autres langages (sauf le C, j'y arrive), et on arrive à écrire des programmes tout à fait corrects. Donc, contrairement à ce que tu sembles dire, utiliser des pointeurs à la place ne veut pas dire "tester la nullité à tout bout de champ".

    Tu argumentes que les références t'apportent une sécurité à peu de frais. Or la façon dont tu garantis que tes références sont valides, c'est par les exceptions. Donc, ce sont les exceptions qui t'apportent une sécurité, et non les références. Par exemple, personne ne teste la nullité de ce que "new" retourne, parce que "new" garantit la non-nullité du pointeur à l'aide d'exceptions. En codant à l'aide d'exceptions, tu n'as pas besoin de vérifier la non-nullité des pointeurs, comme on le fait en Java, comme on le fait en C#, etc.

    La seule raison pour laquelle en C, on teste souvent la nullité des pointeurs, c'est l'absence d'exceptions, et non la sémantique des pointeurs. Ce qui permet d'éviter ces tests, ce ne sont pas les références C++, ce sont les exceptions, comme le démontrent Java et les autres langages supportant les exceptions.

    C'est comme si C++ définissait un type int qui ne peut pas valoir -1 (souvent utilisé comme code d'erreur en C). Que faudrait-il faire? À moins d'avoir un autre mécanisme de gestion d'erreurs (les exceptions), on n'aurait d'autre choix que de continuer à utiliser l'ancien type int. Mais comme les exceptions existent, et qu'on n'a plus besoin de retourner -1, ceux qui souscrivent à ta logique diraient: il faut utiliser le nouveau type int car il garantit que la valeur retournée ne sera pas une erreur. C'est la même chose!

    A quoi te servirait de vouloir modifier une variable anonyme temporaire?
    Prenons l'exemple que tu donnais avec une const string&. Je pourrais vouloir insérer quelque chose au milieu de la string et ensuite l'afficher. Ce n'est pas trop demander j'espère? Or, impossible si la string est const. Je suis contraint d'en créer une copie, ce qui est ridicule. Donc, j'enlève le const, et je ne peux plus passer une variable temporaire.

    En fait, ce qui est ridicule, c'est d'essayer de garantir l'immutabilité d'une variable temporaire. À quoi cela pourrait bien servir? Puisque c'est une variable temporaire, l'appelant ne pourra jamais y accéder après l'appel, justement, donc qu'est-ce que ça peut bien lui faire si elle a été modifiée?

    On ne peut donc pas faire en sorte que new renvoie une référence, simplement parce qu'il renverrait... une référence sur un objet invalidé du fait de sa destruction automatique.
    Je ne comprends pas ce que tu veux dire. new lance une exception s'il ne peut pas allouer de mémoire. Si aucune exception n'est lancée, tu as donc la garantie que ta "référence" est non-nulle. La référence sera invalidée quand l'objet sera détruit, oui, mais c'est ce qui arrive déjà de toute façon! Si je prends une référence sur un objet alloué dynamiquement, et que cet objet est détruit, je me retrouve encore une fois avec une référence invalide. Le fait que new retourne un pointeur ou une référence ne changerait rien à cette situation.

    Tu semblais pourtant dire le contraire
    Pas du tout. J'ai dit "on ne devrait retourner une exception que dans un réel cas d'exception". Une fonction de recherche qui ne trouve rien n'est pas un réel cas d'exception, ni une "erreur": cela fait partie des résultats normaux d'une recherche.

    C++ est réellement orienté objets.
    Ce sera une autre discussion, parce que là je ne suis pas du tout d'accord mais on serait hors sujet.

  7. #47
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    La synchronisation se fait de façon peu coûteuse puisque la librairie utilise des opérations atomiques spécifiques à chaque plateforme plutôt qu'un bête mutex.
    pas sur toutes les platformes cependant, notemment ARM (très très répandu, notamment pour les téléphones) ou mips pour les plus communs. Pour ces platformes, un (bête) mutex est utilisé

  8. #48
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    ...
    je n'ai pas franchement envie de tout quoter, amsi il me semble que dans le cas ou tu dis qu'un objet pointé par une référence peut être effacé et rendre la référence invalide, je dirai que dans ce cas c'est equivalent au pointeur (nu).
    Or il est assez souvent admis que les poineturs nus sont source d'erreur pour le peux de garantie qu'ils proposent. Je les mets personnellement au meme rang que les références (que je n'aime pas) justement pour cela.

    Mais de la a dire que la SL utilise des pointeurs nus, je trouve ca un peu limite. En effet, a part c_str(), je veux bien voir un autre exemple de pointeur nu dans la SL
    la SL utilise plutot des semantiques de valeur, privilégie donc les copies et les références.
    Les pointeurs ne deviennent utiles que pour des sémantiques d'entité. Et la c'est sans doute l'historique qui pousse a utiliser des pointeurs nus. Ou la disparité des implémentations. Qt a par exemple sa propre implémentations, boost la sienne, etc

    Ca ne les empêche pas d'utiliser des pointeurs nus mais la je trouve que c'est une erreur. Ils utilisent les poineturs nus comme des "weak ptr", comme des références en quelque sorte.
    Mais quoi qu'il en soit, dans ces bibliotheques utiliser un pointeur nu c'est s'exposer aux leaks mémoire ou crashs d'acces memoire, la ou les smart pointeurs sauveraient bien des vies. C'est un peu se faire du mal pour rien.


    En conséquences je pense que les references et les pointeurs ne sont pas utilisées de la meme facon; une référence s'utilise avec une semantique de valeur, un pointeur avec une semantique d'entité, et les r´férences sur les entités sont au mieux confuses, et les pointeurs nus sur les entités simplement a proscrire.

  9. #49
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Ce que retourne c_str est un chaine de caractère C-like, alors c'est évident que ca ne peut pas être un pointeur intelligent. Les cas de retour de pointeur brut sont quand même pas si courant dans la S(T)L.

    Pour le point que tu soulèves à propos des variables temporaires que l'on veut modifier, c'est un problème bien connue que la sémantique de "mouvement" du C++1x devrait corriger il me semble.

    Pour les cas d'invalité des références que tu cites, ils sont bien réels. Cependant, ce sont des cas qui ne devrait pas arriver dans un programme bien réalisé, alors que des pointeurs nuls ca arrive à foison, raison pour laquel il faut les tester. Ces cas de référence invalide doivent faire partie de ce koala appelle "les possibilités du C++ de se tirer une balle dans le pied."

    Pour ce qui est de la décision d'un symbole particulier pour signifier la fin d'une séquence, je trouve que c'est bien plus "expressif" que de retourner un pointeur sur NULL, qui permettrait aussi de faire des codes dangereux interdits pour le moment. (les itérateurs ont la sémantique des pointeurs mais sont quand même bien plus adapté pour itérer dans un conteneur, plus simple à incrémenter et à vérifier les bornes).

    [EDIT] Pas d'accord avec ce que tu soulignes en gras, les exception m'assure juste que la fonction s'est bien déroulé, en aucun cas que le pointeur est non nul. Si je vois une fonction avec un pointeur, je suppose directement que le choix d'un pointeur a été fait pour avoir une valeur NULL, et que donc je dois tester avant de déréférencer. [/EDIT]

    Le C++ permet la définition d'objet, ie d'un ensemble de donné qui peut réagir à des messages, le polymorphisme avec un typage statique, je ne vois pas en quoi il ne serais pas orienté objet. Il ne fait peut-etre pas les meilleur choix (dispatch naturel impossible par exemple) mais ca reste OO.

  10. #50
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    Premièrement, la fameuse "garantie" de non-nullité des références est une chimère, puisqu'une référence peut très bien être invalide.
    référence null != dangling référence.
    Et _oui_ une réfèrence ne peut pas être NULL c'est garanti par le standard.

    Ce que retourne c_str est un chaine de caractère C-like, alors c'est évident que ca ne peut pas être un pointeur intelligent. Les cas de retour de pointeur brut sont quand même pas si courant dans la S(T)L.
    +1

    Et
    Pour le point que tu soulèves à propos des variables temporaires que l'on veut modifier, c'est un problème bien connue que la sémantique de "mouvement" du C++1x devrait corriger il me semble.
    Oui (on notera quand même la présence de copy elision et du (N)RVO (non ce n'est pas la même chose) en C++98)
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  11. #51
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Goten Voir le message
    Oui (on notera quand même la présence de copy elision et du (N)RVO (non ce n'est pas la même chose) en C++98)
    Heu, en réalité si, ce qu'on appelle (N)RVO est une partie de ce que le standard (C++1x pas C++03) appelle copy-elision, cf 12.8.34, tu verras que le standard appelle copy-elision les cas classiques que l'on nomme (N)RVO.

  12. #52
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Ce que retourne c_str est un chaine de caractère C-like, alors c'est évident que ca ne peut pas être un pointeur intelligent. Les cas de retour de pointeur brut sont quand même pas si courant dans la S(T)L.
    Je voulais simplement dire que la STL n'expose pas de pointeurs intelligents, mais que lorsqu'elle expose un pointeur, c'est un pointeur brut, en réponse à la question de el_socio.

    Pour les cas d'invalidé des références que tu cites, elles sont bien réel. Cependant, ce sont des cas qui ne devrait pas arriver dans un programme bien réalisé, alors que des pointeurs invalidés ca arrive à foison, raison pour laquel il faut les tester. Ces cas de référence invalide doivent faire partie de ce koala appelle "les possibilités du C++ de se tirer une balle dans le pied."
    J'aimerais bien avoir un exemple de cas où une référence apporte une sécurité supplémentaire à un pointeur, parce que bien honnêtement je ne vois pas pourquoi un pointeur serait plus facile à invalider qu'une référence. Parce qu'une référence est non-réassignable? Un pointeur const ne l'est pas davantage. Enfin, je suis curieux, éclairez-moi donc.

    Pour ce qui est de la décision d'un symbole particulier pour signifier la fin d'une séquence, je trouve que c'est bien plus "expressif" que de retourner un pointeur sur NULL, qui permettrait aussi de faire des codes dangereux interdits pour le moment.
    La STL a la syntaxe la moins expressive de toutes les librairies de conteneurs que je connais. Être obligé d'écrire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    for (letypedemonconteur::const_iterator it = monconteneur.begin(); it != monconteneur.end(); ++it)
    {
    }
    , juste pour traverser un conteneur, c'est le contraire même de l'expressivité. L'existence de BOOST_FOREACH ne fait que le prouver.

    Pas d'accord avec ce que tu soulignes en gras, les exception m'assure juste que la fonction s'est bien déroulé, en aucun cas que le pointeur est non nul. Si je vois une fonction avec un pointeur, je suppose directement que le choix d'un pointeur a été fait pour avoir une valeur NULL, et que donc je dois tester avant de déréférencer.
    Donc tu testes si "new" retourne nul? Inutile, puisqu'il retourne une exception en cas d'erreur. Quand tu programmes en Java, tu testes si chaque référence est nulle? Bien sûr que non, puisqu'encore une fois une fonction bien codée rapporte une erreur par une exception plutôt qu'une pointeur nul. Et qu'est-ce qui te peut te garantir qu'une référence est valide, sinon les exceptions?

  13. #53
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    référence null != dangling référence.
    Mais un crash == un crash.

  14. #54
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    @flob : Ce que je voulias dire c'est que la copy elision c'est plus large que ça, ie ça se résume pas au (N)RVO. (et au passage c'est 12.8.15, t'utilises un draft ?)



    La STL a la syntaxe la moins expressive de toutes les librairies de conteneurs que je connais. Être obligé d'écrire:
    Le prix de la généricité faut croire. Et je suis pas d'accord, la syntaxe est certes lourde, mais pas inexpressive.
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  15. #55
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Citation Envoyé par Goten Voir le message
    Le prix de la généricité faut croire. Et je suis pas d'accord, la syntaxe est certes lourde, mais pas inexpressive.
    Je parle d' "expressivité" dans le sens le plus commun quand on parle de langages de programmation, c'est-à-dire une mesure des idées qui peuvent être exprimées de façon concise et lisible. (lien)

  16. #56
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    [références vs pointeurs]
    Nous avons une différence philosophique d'utilisation du langage qui est à la racine de la différence pointeur vs référence quand on a le choix.

    Pour plusieurs d'entre-nous ici, tout ce que le compilo peut vérifier pour nous à la compilation, on prend!

    Les rares cas où je vais accepter un pointeur sachant que je lui interdis d'être nul (via assert -- et JAMAIS une exception pour traiter une erreur de prog), c'est parce que sémantiquement je vais vouloir tisser un lien vers un autre objet. C'est aussi pour ça que le fait que this soit un pointeur ne me choque pas : pour pouvoir tisser des liens.
    Sinon, je ne cherche pas à comprendre, je forcerai systématiquement le paramètre à être une référence. Un boulet peut m'avoir refilé une référence invalide ? Je commence par le fouetter pour cette erreur de prog et je corrige son erreur et je continue mon chemin.

    Côté attributs, c'est exactement la même histoire. Je rends statiques tous les invariants que je connais par construction du programme (à prendre dans le même sens que "toto a telle propriété par construction" que l'on connait en maths).
    Dans la continuité j'ai très rarement des constructeurs par défaut, et encore plus rarement des mutateurs.
    Aujourd'hui, je bouche le bouchon à écrire "const int i = f(params);". Si si. D'un point de vue maintenance, c'est génial. Là où je vois la déclaration, j'apprends que la valeur ne changera plus. C'est un truc de moins sur lequel réfléchir. Une possibilité de moins que l'on laisse aux reprenneurs pour faire n'importe quoi et altérer l'entrée que l'on utilise en précondition/invariant plus loin.

    Les références, c'est la même chose: une garantie statique.


    [exception, nullité et erreurs de programmation]
    Je reviens là-dessus car je sens que nous touchons ici une autre différence dans nos visions.
    Un erreur de programmation, je vais systématiquement la surveiller avec des assertions. C'est mal-dit. Mes invariants et mes préconditions liés à une mauvaise conception/élaboration d'algo/potentielle erreur , je vais les valider avec des assertions en second lieu. [en premier lieu je m'en remets au compilo].
    En troisième lieu, j'ai des tests unitaires. Et enfin les tests de validation, intégration, et cie.
    Les assertions ont un énorme avantage sur les exceptions : les core dump. C'est un fantastique outil qui nous renvoie le contexte exact de l'erreur (snapshot de la pile de chaque thread + tas + statiques/globales) à contrario des exceptions qui au mieux vont nous renvoyer la liste des fonctions dans la pile et sans les variables.

    Pour en revenir aux références, on (je m'inclue avec les autres) ne garantit pas leur validité par exception, mais par construction (au sens des démonstrations en maths). C'est très différent.

    [Il existe bien un type int qui ne renvoie jamais -1, c'est unsigned int qui m'offre un garantie sémantique : il n'est jamais négatif ; c'est malheureusement difficile à assurer par construction à la différence de la const-correction, et des garanties des références (où il faut vraiment le faire exprès pour renvoyer un truc invalide -- et référence ou pointeur, on ne pourra savoir savoir quand il est dandling de totues façons).


    [Concernant la MT-safety des shared_ptr<>]
    Euh ... C'est quoi le problème ? Les shared_ptr les mêmes garanties que les pointeurs bruts, plus une petite garantie relative à l'incrément des références. Partager avec possibilité de modification une même variable pointeurs entre plusieurs threads me parait le b-à-ba du n'importe quoi, au contraire de partager une même ressource qui sera derrière un pointeur à comptage de référence incrémentée atomiquement.


    [Pour la perf de new/delete]
    ... pools ? Ce que fais Java en natif, quoi. A sortir quand les benchmarks nous disent que le problème est là. Un faux débat sinon.


    [bad_alloc]
    @el socio, j'ai déjà eu un new qui a échoué -- un resize() en fait, mais c'est pareil. Un couillon de pair qui me balançai un longueur de message négative sur notre socket et moi qui allouait innocemment. Heureusement je ne plantais pas, mais je coupais la connexion correspondante, ce qui était à peine mieux.
    [Et cette situation n'engendrait pas de fuite pour autant, vu comme il est simple d'être leak-free avec le RAII. Avoir un serveur robuste qui ne s'arrête jamais, je n'appelle pas ça être universitaire.]
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  17. #57
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Salut Luc Hermitte,

    Pour plusieurs d'entre-nous ici, tout ce que le compilo peut vérifier pour nous à la compilation, on prend!
    Pourquoi? Je me donne la liberté de remettre cette idée en question, puisque les idées pré-reçues sont l'ennemies de la logique. L'idée derrière cette "règle" est que les erreurs de compilation sont plus faciles à régler que les erreurs d'exécution.

    Or, en C++, les erreurs qui m'ont fait perdre le plus de temps sont des erreurs de compilation, surtout lorsqu'on prend en compte qu'il est exceptionnellement long de compiler un programme C++ et donc d'obtenir ces erreurs. J'ai appelé à mon aide des programmeurs d'expérience (+ de 20 ans d'expérience en C++, difficile de faire mieux), et ils n'ont pas trouvé. J'ai perdu des semaines à faire compiler du C++, et je ne suis pas le seul. Quand on sait à quel point les messages d'erreurs de compilateurs peuvent être incompréhensibles surtout quand on utilise Boost et autres casses-têtes de templates, que le compilateur peut simplement planter, sans la moindre indication, à cause d'une faute de frappe indiscernable, on en vient à préférer parfois les erreurs à l'exécution, erreurs que les tests unitaires détectent de façon méthodique et fiable.

    Un peu d'expérience dans d'autres langages ne fait pas de tort non plus. En Python, par exemple, il n'existe pas de "const". Il n'y a pas de "référence qui ne peut pas être nulle". Et pourtant, des systèmes complexes et très fiables sont développés en Python, bien plus rapidement qu'en C++ d'ailleurs. Après avoir écrit un programme de taille moyenne en Python, on revient en C++ et on se dit "mais pourquoi je me donne tout ce mal?"

    Et donc, je n'accepte pas comme idée reçue que "tout ce que le compilateur peut vérifier, on le prend". Malheureusement les programmeurs C++ prennent trop souvent le C comme point de comparaison: "mieux que le C", ce n'est pas encore si formidable que ça.

    Pour en revenir aux références, on (je m'inclue avec les autres) ne garantit pas leur validité par exception, mais par construction (au sens des démonstrations en maths). C'est très différent.
    Je suis bien d'accord qu'on ne doit pas camoufler un bogue par une exception. Or, un programme n'interagit pas qu'avec lui-même, mais aussi avec le monde extérieur, qu'il ne contrôle pas. Et là où les hypothèses qu'il fait sur le monde extérieur échouent, il rencontre une situation d'exception et se voit incapable de respecter sa propre spécification, en quel cas il devrait rapporter la situation aux instances supérieures (le client) par le moyen d'une exception.

    Pour prendre la STL comme exemple encore une fois, la fonction vector::at(int index) retourne une référence sur l'objet situé à index, et lance une exception sinon. Donc, elle garantit la validité de la référence à l'aide d'une exception. Je ne vois pas comment la STL pourrait éviter de lancer une exception par "construction" étant donné que l'entrée ici (l'index) vient du monde extérieur (l'utilisateur) et est par conséquent incontrôlable. Si la STL ne lançait pas d'exception, elle serait contrainte de retourner soit une référence invalide (affreux), ou un pointeur nul (un peu mieux), ou un code d'erreur (...) mais le mieux est de lancer une exception.

    Partager avec possibilité de modification une même variable pointeurs entre plusieurs threads me parait le b-à-ba du n'importe quoi
    Comme j'ai dit, shared_ptr est plus thread-safe que je me le représentais initialement, et est donc une solution viable dans un contexte multi-threads, je suis d'accord.

    [Pour la perf de new/delete]
    ... pools ? Ce que fais Java en natif, quoi. A sortir quand les benchmarks nous disent que le problème est là. Un faux débat sinon.
    Il est tout à fait possible d'avoir un système de gestion mémoire similaire à Java en C++, mais ce n'est pas les pointeurs intelligents qui l'implémentent. Le danger est de croire que oui. C'est tout ce que j'ai dit.

  18. #58
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Pour ce qui est de ce qui vient du monde extérieur, tu cites at mais son comportement est exceptionnel dans la STL, la philosophie de la STL est plutôt : tu ne respectes pas les pré-requis, alors moi je fais n'importe quoi, c'est comme ca que fonctionne [] et la plupart des fonction de la STL.

    Pour l'idée de ce qui est vérifiable à la compilation est bon à prendre, je ne vois pas tes arguments contre : tout ce qui est fait à la compilation est fixé, ca ne varira plus, c'est une garantie forte que ces points seront justes.

    Si j'arrive à prouver (ou vérifier avec le compilo) qu'une de mes variables est inclus dans un certain intervalle, je vois difficilement pourquoi je ne considérerais pas cette information et reppouserais cette vérification à l'execution, à part une perte de perf je vois aucun avantage.

    Pour la verbosité des erreurs en C++, je suis bien d'accord, surtout quand les template et la STL s'en mêlent, mais je ne pense pas que ce soit un argument pour dire que les vérifications à la compilation sont une mauvaise chose, surtout qu'il me semble qu'il existe des outils pour rendre ces messages plus clair.

  19. #59
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    a- Pourquoi? Je me donne la liberté de remettre cette idée en question, puisque les idées pré-reçues sont l'ennemie de la logique. L'idée derrière cette "règle" est que les erreurs de compilation sont plus faciles à régler que les erreurs d'exécution.

    b- Or, en C++, les erreurs qui m'ont fait perdre le plus de temps sont des erreurs de compilation, surtout lorsqu'on prend en compte qu'il est exceptionnellement long de compiler un programme C++ et donc d'obtenir ces erreurs. J'ai appelé à mon aide des programmeurs d'expérience (+ de 20 ans d'expérience en C++, difficile de faire mieux), et ils n'ont pas trouvé. J'ai perdu des semaines à faire compiler du C++, et je ne suis pas le seul. Quand on sait à quel point les messages d'erreurs de compilateurs peuvent être incompréhensibles surtout quand on utilise Boost et autres casses-têtes de templates, que le compilateur peut simplement planter, sans la moindre indication, à cause d'une faute de frappe indiscernable, on en vient à préférer parfois les erreurs à l'exécution,

    c- erreurs que les tests unitaires détectent de façon méthodique et fiable.

    d- Un peu d'expérience dans d'autres langages ne fait pas de tort non plus. En Python, par exemple, il n'existe pas de "const". Il n'y a pas de "référence qui ne peut pas être nulle". Et pourtant, des systèmes complexes et très fiables sont développés en Python, bien plus rapidement qu'en C++ d'ailleurs. Après avoir écrit un programme de taille moyenne en Python, on revient en C++ et on se dit "mais pourquoi je me donne tout ce mal?"

    e- Et donc, je n'accepte pas comme idée reçue que "tout ce que le compilateur peut vérifier, on le prend". Malheureusement les programmeurs C++ prennent trop souvent le C comme point de comparaison: "mieux que le C", ce n'est pas encore si formidable que ça.

    f- Je suis bien d'accord qu'on ne doit pas camoufler un bogue par une exception. Or, un programme n'interagit pas qu'avec lui-même, mais aussi avec le monde extérieur, qu'il ne contrôle pas. Et là où les hypothèses qu'il fait sur le monde extérieur échouent, il rencontre une situation d'exception et se voit incapable de respecter sa propre spécification, en quel cas il devrait rapporter la situation aux instances supérieures (le client) par le moyen d'une exception.

    g- Pour prendre la STL comme exemple encore une fois, la fonction vector::at(int index) retourne une référence sur l'objet situé à index, et lance une exception sinon. Donc, elle garantit la validité de la référence à l'aide d'une exception. Je ne vois pas comment la STL pourrait éviter de lancer une exception par "construction" étant donné que l'entrée ici (l'index) vient du monde extérieur (l'utilisateur) et est par conséquent incontrôlable. Si la STL ne lançait pas d'exception, elle serait contrainte de retourner soit une référence invalide (affreux), ou un pointeur nul (un peu mieux), ou un code d'erreur (...) mais le mieux est de lancer une exception.
    a- ce n'est pas une idée préconçue, mais l'état actuel de mon opinion sur le sujet, opinion induite par mes expériences.
    Je perds plus de temps à traquer des dérèglements induits par les laxismes des C&C++ qu'à comprendre les erreurs de compilation qui me sont sortis.
    Et franchement, ce ne sont pas quelques const, quelques références, voire des volatiles (cf le détournement du mot clé par A.Alexandrescu), et autres invariants d'immuabilité pour vont me causer des complications pour compiler.

    b- 20 d'expérience en C++ n'est pas exactement un critère au vu des révolutions perpétuelles dans ses paradigmes (les templates ont moins de 15 ans, IIRC, et la meta-prog template source de tant de difficultés encore moins)
    Ceci dit, j'ai beaucoup de respects pour certains développeurs qui ont cette expérience dans ce langage également. Je n'ai jamais autant appris qu'en fréquentant fclc++.

    J'y pense, pour boost c'est insuffisant (il faudra, disons, rajouter les substitute qui vont bien), mais pour la SL, STLfilt est notre très précieux ami. Je suis régulièrement surpris par le faible pourcentage de développeurs C++ ayant croisé le chemin de cet indispensable outil.

    c- Les TU ne me servent qu'à vérifier les algos en C++. C'est une chance que nous avons comparativement aux langages interprétés où il faut cette fois vérifier bien plus de choses.

    d- Autre expérience : GWT qui vante les mérite de la compilation. Et j'ai vraiment été convaincu après expérimentation.
    La force de python, se sera de pouvoir prototyper rapidement, si on le compare au C++. Mais le temps que l'on aurait pu passer à prendre un café, on va l'avoir perdu à développer des TU qui se substituent à la phase de compilation -- et qui ne remplacent pas les TU vérifiant les algos.

    e- Oh que non. Le C++ est loin d'être le seul langage que nous connaissons. Comment le maitriser si on ne connait que lui et le C?
    Avec tous mes const et autres "les mutateurs ne passeront pas moi", j'expérimente justement des propriétés tirées de langages où la mu(t?)abilité n'existe pas.

    f- nous sommes d'accord -- de même que pour les parties de la fin que je n'ai pas citées.

    g- Je n'utilise jamais at(). Mais vraiment. Jamais. Je la vois comme une fonction pour faire plaisir. En ce qui me concerne, elle n'existe pas. Mon seul point d'entrée est operator[], et j'ai une nette préférence pour ses implémentations qui claquent une assertion sur dépassement de bornes -- pour la raison que j'ai citée: se planter dans les bornes, c'est une erreur de programmation. Malheureusement, ce n'est pas spécifié ainsi.

    Par construction mon indice est toujours valide avant de demander quoique ce soit au vecteur, soit parce que j'ai fait une vérification sur une entrée non maitrisée, et donc je pars faire autre chose, soit parce que je suis dans un contexte très précis : boucle, résultat de find, etc.

    Disons que at() c'était un mauvais exemple. Disons aussi que quand je sais qu'une fonction peut ne pas renvoyer un truc, alors je vais renvoyer un pointeur. Et sans hésiter (j'ai dit "quand on a le choix", non ? bon, c'est vrai que nous aurions le choix, mais ça serait maladroit de mon avis) (à part avec un itérateur/indice suivant ce que je dois faire ensuite).
    OK, je vois le rapport en exception et référence dans ta prose. Pointeur je prends ici dans les situations de recherche. at() n'existant pas ...
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  20. #60
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    Salut Luc Hermitte,

    Pourquoi? Je me donne la liberté de remettre cette idée en question, puisque les idées pré-reçues sont l'ennemies de la logique. L'idée derrière cette "règle" est que les erreurs de compilation sont plus faciles à régler que les erreurs d'exécution.

    Or, en C++, les erreurs qui m'ont fait perdre le plus de temps sont des erreurs de compilation, surtout lorsqu'on prend en compte qu'il est exceptionnellement long de compiler un programme C++ et donc d'obtenir ces erreurs.
    Le fait est que, même s'il faut, effectivement, avouer que certaines erreurs de compilation sont particulièrement difficilement compréhensibles, une fois que tu as corrigé l'erreur de compilation, tu as fini avec cela, alors qu'une erreur de logique ou d'exécution, elle peut survenir n'importe quand
    J'ai appelé à mon aide des programmeurs d'expérience (+ de 20 ans d'expérience en C++, difficile de faire mieux),
    Ils faut aussi voir lesquels...

    Certains programment encore exactement comme il le faisaient il y a 20 ans, et ce ne sont donc pas forcément des références
    et ils n'ont pas trouvé. J'ai perdu des semaines à faire compiler du C++, et je ne suis pas le seul. Quand on sait à quel point les messages d'erreurs de compilateurs peuvent être incompréhensibles surtout quand on utilise Boost et autres casses-têtes de templates, que le compilateur peut simplement planter, sans la moindre indication, à cause d'une faute de frappe indiscernable,
    Et pourtant, j'en suis arriver (comme de nombreux autres ici, d'ailleurs) à configurer mon compilateur pour qu'il me sorte un maxium d'avertissements et pour veiller à tous les prendre en compte...

    Je le répète, il faut effectivement un minimum d'habitude pour comprendre certaines erreurs, mais, je gagne au final beaucoup plus de temps à apporter une solution aux problèmes que mon compilateur me signale qu'à attendre qu'un test unitaire me fasse, justement, comprendre que je me suis planté quelque part
    on en vient à préférer parfois les erreurs à l'exécution, erreurs que les tests unitaires détectent de façon méthodique et fiable.
    Si ce n'est, encore une fois (je crois que j'en ai déjà parlé dans cette discussion... ou dans l'une de celles que j'ai citées ) que, malgré la compétence de ceux qui mettent les test unitaires au point et la qualité de ceux-ci, tout ce qu'ils prouvent, c'est que l'on n'a pas été en mesure de prendre les parties testées en faute, absolument pas que les parties testées sont exemptes d'erreur
    Un peu d'expérience dans d'autres langages ne fait pas de tort non plus. En Python, par exemple, il n'existe pas de "const". Il n'y a pas de "référence qui ne peut pas être nulle". Et pourtant, des systèmes complexes et très fiables sont développés en Python, bien plus rapidement qu'en C++ d'ailleurs. Après avoir écrit un programme de taille moyenne en Python, on revient en C++ et on se dit "mais pourquoi je me donne tout ce mal?"
    Ne crois pas que personne n'ait d'expérience dans d'autres langages que C++, c'est loin d'être vrai.

    Ce qui se passe, c'est que tout le monde est d'accord sur un point: C++ est autrement plus complexe, permet beaucoup plus facilement de se "tirer une balle dans le pied" que de nombreux autres langages.

    C'est la raison pour laquelle on préférera permettre au compilateur de vérifier le plus de choses possibles, car quand on résout les problèmes qu'il met en avant, on s'évite de nombreuses erreurs.
    Et donc, je n'accepte pas comme idée reçue que "tout ce que le compilateur peut vérifier, on le prend". Malheureusement les programmeurs C++ prennent trop souvent le C comme point de comparaison: "mieux que le C", ce n'est pas encore si formidable que ça.
    Et tu as bien tord... Mais bon, je ne vais pas me répéter
    Je suis bien d'accord qu'on ne doit pas camoufler un bogue par une exception. Or, un programme n'interagit pas qu'avec lui-même, mais aussi avec le monde extérieur, qu'il ne contrôle pas. Et là où les hypothèses qu'il fait sur le monde extérieur échouent, il rencontre une situation d'exception et se voit incapable de respecter sa propre spécification, en quel cas il devrait rapporter la situation aux instances supérieures (le client) par le moyen d'une exception.

    Pour prendre la STL comme exemple encore une fois, la fonction vector::at(int index) retourne une référence sur l'objet situé à index, et lance une exception sinon. Donc, elle garantit la validité de la référence à l'aide d'une exception. Je ne vois pas comment la STL pourrait éviter de lancer une exception par "construction" étant donné que l'entrée ici (l'index) vient du monde extérieur (l'utilisateur) et est par conséquent incontrôlable. Si la STL ne lançait pas d'exception, elle serait contrainte de retourner soit une référence invalide (affreux), ou un pointeur nul (un peu mieux), ou un code d'erreur (...) mais le mieux est de lancer une exception.
    Le fait est que la fonction at n'est jamais qu'une version sécurisante (dans le sens où elle constate le dépassement d'index) de l'opérateur [].

    Comme un vecteur peut contenir strictement n'importe quoi (pour autant qu'il soit copiable), on ne pouvait ni renvoyer un itérateur (car at ne renvoie pas un itérateur) ni NULL (car at ne renvoie pas de pointeur) ni quoi que ce soit de "plus ou moins commun".

    Elle pourrait utiliser une assertion, mais elle ne serait donc sécurisée que... en mode débug

    La seule solution qui reste, afin d'assurer cette sécurisation, c'est l'exception
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Réponses: 55
    Dernier message: 18/03/2014, 12h11
  2. Réponses: 13
    Dernier message: 27/04/2012, 16h03
  3. Les pointeurs intelligents
    Par MatRem dans le forum C++
    Réponses: 8
    Dernier message: 20/06/2006, 19h27
  4. pointeur intelligent??
    Par yashiro dans le forum C++
    Réponses: 3
    Dernier message: 04/04/2006, 08h08
  5. Pointeur intelligent boost : return NULL ->comment faire?
    Par choinul dans le forum Bibliothèques
    Réponses: 7
    Dernier message: 21/12/2005, 16h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo