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

Langage C++ Discussion :

Perte de sens du C++11


Sujet :

Langage C++

  1. #1
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut Perte de sens du C++11
    Salut à tous

    Bon, pas d’inquiétude avec mon titre trollesque, j'attaque pas le C++11.

    Je discutais avec germinolegrand sur le chat suite à ce bout de code qu'il me présente :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::list<std::pair<sf::Vector2f, sf::ConvexShape>> m_triangles;
    auto it = std::find_if(begin(m_triangles), end(m_triangles), [pos](const std::pair<sf::Vector2f, sf::ConvexShape> &p){return p.first == pos;});
    Jusque là, rien à dire, utilisation classique des lambdas...

    En bien si, je critique (comme d'habitude )

    On comprend que l'on a une liste de triangles avec le nom de la variable, mais les triangles, ce sont std::pair<sf::Vector2f, sf::ConvexShape> ou sf::ConvexShape ? A quoi correspond sf::Vector2f ? Ok, on fait un find pour trouver le triangle qui à un sf::Vector2f donné, mais ça correspond à quoi ? Trouver les triangles qui ont un point commun ? Le même centre ?

    Avec les facilités d'écriture du C++11, ne risque-t-on pas de voir une utilisation à outrance, quitte à perdre de la sémantique ?

    Sur l'exemple de code précédent, j'aurais plus vu l'utilisation d'une classe (je simplifie le code un peu, en particulier au niveau des accès) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    struct Triangle {
        sf::Vector2f center;
        sf::ConvexShape points;
        bool operator== (Triangle const& t) const { return t.center == center; }
    };
     
    auto it = std::find(begin(m_triangles), end(m_triangles), pos);
    Dans l'idée qu'une classe est un contrat entre le dev d'une classe et les utilisateurs de cette classe, créer un tel objet permet de :
    1. définir correctement un sémantique sur Triangle (de valeur ici), c'est à dire comment doit être compris cette classe, ce quelle représente et donc que l'on peut faire (ou pas) avec cette classe
    2. de contrôler les accès aux variables membres (rien n'interdit dans le code avec pair de modifier le centre d'un triangle, ce qui n'aurait pas de sens)
    3. que le lecteur sache à quoi correspond les variables qu'il manipule. sf::Vector2f peut être un angle, le barycentre, le centre des cercles conscrit ou circonscrits, etc.

    Idem pour le find. On peut le laisser tel quel dans le code. Ou on peut aussi l'encapsuler dans une fonction pour lui donner un sens :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<class TRIANGLE>
    auto findCorrespondingTriangle(typename TRIANGLE::CenterType const& pos, list<TRIANGLE> const& triangles) {
        return std::find(begin(triangles), end(triangles), pos);
    }
    (bon, c'est l'idée. Pour mieux faire, on peut également ajouter une abstraction pour le type de conteneur et proposer une politique pour le type de correspondance que l'on recherche : triangles avec centre en commun, triangles sécants, etc)


    Bon, sur 2 lignes de code, c'est un peu caricatural. Mais j'ai l'impression que l'on voit de plus en plus ce genre de code, où l'auteur pense (à tort à mon avis, si c'est pour économiser 2 lignes) que plus c'est court, mieux c'est.
    Vous en pensez quoi ?

  2. #2
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Salut,

    J'ai l'impression que tu te complique la vie pour rien. Le langage permet l'obfuscation, mais il est bien assez riche pour l'éviter, il ne tient qu'aux développeurs d'y prendre garde.

    Vouloir faire une classe pour ces deux champs me semble être une erreur, et de manière général vouloir faire de l'objet sur une séquence de tuple (ie une BDD) me semble être une abstraction non nécessaire et qui peut apporter des problèmes (il me semble que Emmanuel Deloget a faire un article là dessus d'ailleurs).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    typedef sf::Vector2f CenterType;
    typedef sf::ConvexShape TriangleType;
    typedef std::tuple<CenterType,TriangleType> EntityType;
    typedef std::list<EntityType> DataBaseType;
     
    enum CaseName {CenterCase,TriangleCase};
     
    DataBaseType data_base;
    auto it = std::find_if(
      begin(data_base),
      end(data_base),
      [&center](const EntityType& entity){
        return  get<CenterCase>(entity) == center;
      }
    );
    NB: Je ne connais pas le vocabulaire des bases de données, il y a peut-être plus adapté.

    Personnellement, écrit comme ça, je trouve ça lisible (mis à par les typedef à adpater pour le vocabulaire).

    PS: Code non testé, c'est juste pour l'idée.

  3. #3
    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 : 50
    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
    Par défaut
    Une partie du problème prédate largement le C++11 : Il s'agit de std::pair. D'un certain point de vue, ce n'est rien d'autre qu'une classe à deux membres pour laquelle on a été trop fainéant pour trouver un nom (bon, ok, avec tuple, C++11 pousse ça dans une nouvelle dimension).

    Et pour moi, utiliser std::pair pour remplacer une vraie classe, avec ses invariants, son utilisation dans plusieurs contextes... a toujours été une hérésie (j'aurais d'ailleurs bien aimé une map dont je puisse décider du value_type et du membre qui constitue la clef).

    Maintenant, il faut avouer que quand la pair n'est utilisée qu'à un seul endroit, ou quand on fait une bibliothèque assez bas niveau pour laquelle on ne sait pas associer de sens métier à first et second, et bien std::pair est assez adapté.

    Je pense que c'est pareil pour le find. D'ailleurs, pour moi, ici, le problème n'est pas que le lambda et le fait qu'il n'est pas nommé, mais juste que l'on a un find spécialisé non nommé, le problème serait identique si on avait non pas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    auto it = std::find_if(begin(m_triangles), end(m_triangles), [pos](const std::pair<sf::Vector2f, sf::ConvexShape> &p){return p.first == pos;}
    mais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    auto it = std::find_if(begin(m_triangles), end(m_triangles), IsAtSamePos(pos));
    Qui reste bien moins clair que ton findCorrespondingTriangle (même si je n'aime pas la manière dont tu l'as déclaré, mais c'est un autre sujet, c'est son existence qui compte).

    Si on doit rechercher ainsi une seule fois un triangle, dans le cadre d'une algorithme plus évolué, j'aime bien la version avec lambda non encapsulée. S'il s'agit d'un service à rendre en tant que tel, de manière répétée, et bien, je ferais soit une fonction libre, soit carrément une classe gérant un ensemble de triangles, avec ses propres invariants, etc. Et dans les deux cas, j'aurais un test unitaire de cette fonction.
    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.

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    J'aurais tendance à plussoter Loïc...

    Le problème ne vient pas des lambda de C++11, mais de la std::pair.

    Après tout, une std::pair n'est jamais... qu'un ensemble de données pour lesquels on a décidé qu'elle allaient bien l'une avec l'autre.

    Il se fait que la STL considère plus pratique d'utiliser cet ensemble de données lorsqu'il s'agit de gérer des tableaux associatifs clés / valeurs (les std::map, si vous n'aviez pas traduit ) en donnant un sens particulier à chaque donnée (la clé pour first, la valeur pour second), avec tout ce que cela implique.

    Il se fait que, contrairement à std::map, la std::list
    • n'offre aucune garantie d'unicité des clés
    • n'offre aucune garantie de tri par rapport aux clés
    • n'offre aucune garantie d'accès en O(log N) sur base des clés
    Il n'est donc pas étonnant que quelqu'un puisse envisager d'utiliser une std::pair pour... ce que c'est: deux valeurs de type (éventuellement) distinct qui vont particulièrement bien ensemble, pour une raison qui n'appartient qu'à celui qui décide de l'utiliser.

    A partir de là, il faut bien fournir "quelque chose" qui permette à std::find de fonctionner.

    L'utilisateur a décidé d'utiliser une expression lambda, et, pour ma part, tant que l'utilisateur n'utilise qu'une fois ce code pour rechercher un élément, il aurait sans doute tort de se priver des lambda quand on sait ce que nous couterait le fait de s'en passer.

    En effet, les solutions pour s'en passer vont de la création d'une fonction libre à la création pure et simple d'un foncteur, ce qui peut réellement sembler excessif (en C++11 du moins) si l'objectif n'est d'utiliser ce code de recherche particulier qu'une seule et unique fois.

    Par contre, on peut réellement se poser la question de savoir si l'utilisateur n'aurait pas eu plus intérêt à créer sa propre structure, avec des noms plus cohérents (par rapport à l'usage qui en est fait) pour représenter cette paire de données.

    Et, même sur ce point, on peut encore pinailler

    En effet, si la std::pair<sf::Vector2f, sf::ConvexShape> n'est utilisée qu'à usage interne, bah, ma foi... on peut juste regretter que cela nous impose une dépendance supplémentaire et que cela nous fasse perdre un peu de lisibilité (et surtout "d'auto commentarité") au niveau du code.

    Bien sur, il aurait sans doute été préférable que l'utilisateur nous crée une jolie structure avec un nom qui indique clairement à quoi elle sert, et des noms pour les deux données qu'elle représente qui, là aussi, nous donnent une indication précise de ce à quoi on a à faire, mais il ne s'agit, au pire, que d'une violation de "coding rules" propres au projet, si tant est qu'il en existe

    Ceci dit, je reconnais sans conteste qu'il y a largement moyen de mieux faire au niveau de la lisibilité du code.

    Je crois cependant que ce n'est pas du à l'expression lambda, ni même à l'utilisation du terme auto (car ce sont les deux seules possibilités offertes par C++11 qui sont en cause), mais bien que c'est du à une utilisation irraisonnée de std::pair, et ca, ca existe depuis longtemps
    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

  5. #5
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    J'ai l'impression que tu te complique la vie pour rien. Le langage permet l'obfuscation, mais il est bien assez riche pour l'éviter, il ne tient qu'aux développeurs d'y prendre garde.
    J'avais prévenu, l'exemple est un peu caricatural
    Mais le but est bien cette mise en garde : c'est pas parce que l'on peut le faire qu'il faut le faire

    Et pour moi, utiliser std::pair pour remplacer une vraie classe, avec ses invariants, son utilisation dans plusieurs contextes... a toujours été une hérésie (j'aurais d'ailleurs bien aimé une map dont je puisse décider du value_type et du membre qui constitue la clef).

    Maintenant, il faut avouer que quand la pair n'est utilisée qu'à un seul endroit, ou quand on fait une bibliothèque assez bas niveau pour laquelle on ne sait pas associer de sens métier à first et second, et bien std::pair est assez adapté.
    On est d'accord. Perso, utiliser pair ou tuple en interne dans une fonction ou entre fonction membres (donc comme alternative à une nested class) me choque pas.
    Par contre, manipuler des pair et tuple entre plusieurs classes commence à me faire réfléchir (je l'interdit pas, mais je me pose la question de créer une classe). Même un pair<bool, Result> me plait pas trop (même si pour des raisons de facilité, on utilise souvent)

    Or ici, on parle de triangles. Donc probablement qu'un moment donné il faudra les créer, les lire dans un fichier, les afficher, peut être faire des manipulations géométriques dessus... Ça commence à faire beaucoup pour un simple pair
    Finalement, utiliser pair et tuple revient à concevoir les objets que comme un regroupement de données, sans signification particulière et surtout sans contraintes internes (pas d'invariant de classe à vérifier, pas de conditions sur les paramètres). Bref, on en revient au problème classique de concevoir les classes comme conglomérat de données et plus comme fournisseur de services.

  6. #6
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    (EDIT grillé par koala01)

    Bien sur, il aurait sans doute été préférable que l'utilisateur nous crée une jolie structure avec un nom qui indique clairement à quoi elle sert, et des noms pour les deux données qu'elle représente qui, là aussi, nous donnent une indication précise de ce à quoi on a à faire, mais il ne s'agit, au pire, que d'une violation de "coding rules" propres au projet, si tant est qu'il en existe

    Ceci dit, je reconnais sans conteste qu'il y a largement moyen de mieux faire au niveau de la lisibilité du code.
    C'est bien sur un choix de conception. Mais comme dit précédemment, pour moi, il y a suffisamment de choisir à faire sur un triangle pour justifier la création d'une classe. Et le création d'un opérateur == (sémantique de valeur)

    Je crois cependant que ce n'est pas du à l'expression lambda
    J'ai quand même l'impression que les lambdas favorise le raisonnement en termes de regroupement de données. L'utilisateur réfléchit à comment il va manipuler les données, au moment où il les manipule, et l'expression de cette réflexion est le lambda (code local au moment de l'utilisation qui manipule directement les données)
    Sans les lambdas, il me semble que l'on est plus obligé de réfléchir à partir de la classe et des services qu'elles fournit. Dans l'exemple, le code permettant de vérifier l'égalité est dans le lambda, c'est à dire dans le code utilisateur, alors que l'égalité des triangles devrait être intrinsèque au type triangle.


    Autre façon de voir les choses.
    Si on est un vieux de la veille, on va peut être partir sur la définition d'une classe complète, avec 50 fonctions membres et tous les services possibles et imaginables.
    Si on est un petit jeune qui tente d'être "agile", on va écrire que le code que l'on a besoin, au moment où l'on en a besoin. On va se faire un pair ou un tuple et se faire un lambda là où on utilise notre triangle. Ok. Puis on va manipuler notre triangle à un autre endroit. Et on va créer un second lambda (donc le code sera peut être identique au premier). Puis un troisième endroit et un troisième lambda. Et on réalise alors que finalement, c'est peut être pas la bonne approche, mais la refactorisation coûtera trop chère...

    Bref, les pair, tuple et lmabda sont des fonctionnalités qui permettent de gagner du temps et des lignes de code. Mais cela ne dispense pas de bien réfléchir à sa conception avant


    @JolyLoic moi non plus j'aime pas le définition de ma fonction

  7. #7
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Finalement, utiliser pair et tuple revient à concevoir les objets que comme un regroupement de données, sans signification particulière et surtout sans contraintes internes (pas d'invariant de classe à vérifier, pas de conditions sur les paramètres). Bref, on en revient au problème classique de concevoir les classes comme conglomérat de données et plus comme fournisseur de services.
    Totalement d'accord, et je suis d'accord avec Philippe et Loic, si l'on peut faire une classe avec une sémantique bien déterminé, alors il faut le faire, avec les services et tout ce qui va bien.

    Ceci dit, le genre de code que tu montres m'a fait penser à une situation de base de donnée que l'on veut manipuler directement en C++. Et dans ce cas, faire un modèle objet me semble une abstraction totalement superflux : le modèle qui colle le plus est une séquence de tuple.

    Si la situation concrète n'est pas celle-ci, alors oui, je suis aussi pour définir une classe et des services.

    Pour l'article d'Emmanuel auquel je pense http://blog.emmanueldeloget.com/inde...ne-bonne-chose (je ne l'ai pas relu, c'est peut-être pas le bon).

  8. #8
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par gbdivers Voir le message
    (EDIT grillé par koala01)


    C'est bien sur un choix de conception. Mais comme dit précédemment, pour moi, il y a suffisamment de choisir à faire sur un triangle pour justifier la création d'une classe. Et le création d'un opérateur == (sémantique de valeur)


    J'ai quand même l'impression que les lambdas favorise le raisonnement en termes de regroupement de données. L'utilisateur réfléchit à comment il va manipuler les données, au moment où il les manipule, et l'expression de cette réflexion est le lambda (code local au moment de l'utilisation qui manipule directement les données)
    Sans les lambdas, il me semble que l'on est plus obligé de réfléchir à partir de la classe et des services qu'elles fournit. Dans l'exemple, le code permettant de vérifier l'égalité est dans le lambda, c'est à dire dans le code utilisateur, alors que l'égalité des triangles devrait être intrinsèque au type triangle
    Je crois que les habitués du forum me connaissent assez que pour savoir que je suis particulièrement attaché au principes S.O.L.I.D, et je ne peux donc qu'être tout à fait d'accord avec toi quant au fait qu'une classe clairement définie, avec une responsabilité clairement définie et des services associés aurait sans conteste été un bien meilleur choix que la std::pair...

    Je ne reviens absolument pas là dessus.

    Je t'accorde d'autant plus volontiers le fait, l'expression lambda aurait sans doute eu énormément d'avantage à se trouver dans un foncteur particulier que je suis tout à fait de ton avis sur ce sujet aussi

    Cependant, ces deux avis, auxquels je tiens fondamentalement, sont essentiellement justifiés dans le cas où les données manipulées (et le foncteur qui va avec) sont accessibles "à peu près partout".

    Par contre, si tu as une classe et une séries de foncteurs qui vont avec et qui sont utilisés de manière plus ou moins récurrente et que, dans un cas bien particulier (et j'insiste sur ce terme ) , tu te trouves dans une situation où tu as besoin d'une comparaison différente pour une raison qui est propre à un comportement bien particulier, je peux concevoir que l'expression lambda permet de faire les choses beaucoup plus facilement et rapidement.

    C'est exactement le sens général de mon intervention précédente où je disais
    Citation Envoyé par moi meme
    tant que l'utilisateur n'utilise qu'une fois ce code pour rechercher un élément
    et
    Par contre, on peut réellement se poser la question de savoir si l'utilisateur n'aurait pas eu plus intérêt à créer sa propre structure, avec des noms plus cohérents (par rapport à l'usage qui en est fait) pour représenter cette paire de données.
    sous entendu soit une simple structure "classique" (oserais-je dire C style ) soit une classe avec tout ce qui permet de respecter l'OCP

    Mais, avant de taper l'utilisateur pour l'utilisation de l'expression lambda, je le taperai beaucoup plus surement pour l'utilisation "abusive" de la std::pair, dans le cas présent (et sans savoir l'utilisation qui peut etre faite de cette structure de donnée par ailleurs )
    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

  9. #9
    Membre Expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Par défaut
    Pour répondre à l'utilisation de std::pair, je vais préciser ce qui motive son utilisation, bien qu'en y repensant std::tuple est peut-être plus adapté.

    Les deux éléments du pair ne sont qu'une seule et même donnée exprimée sous différentes formes (entendez qu'à partir de la première on peut calculer la deuxième et inversement). C'est donc une information redondante, toutefois cela évite un calcul lourd et fastidieux de conversion à chaque fois qu'on a besoin de manipuler ces données sous une forme ou sous une autre. La première forme sert à faire tous les calculs de collision, d'existence, etc. La 2e sert à l'affichage, et la recréer à chaque frame serait pure perte de performance. La 2e contient d'autres données, toutefois en l'état actuel des choses ces données ne sont pas utilisées/déterminables par calcul depuis la 1e forme.

    Pour le sf::Vector2f et la dépendance que cela crée avec SFML, je dirais que je prend le parti d'exploiter un framework au maximum.

    Pour ce qui est de s'en servir une seule fois de cette lambda, je dirais que si j'avais à la réutiliser je me contenterais de rajouter une fonction decltype(m_triangles)::iterator findTriangle(const sf::Vector &center) qui contiendrait simplement cette ligne.

  10. #10
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    Pour répondre à l'utilisation de std::pair, je vais préciser ce qui motive son utilisation, bien qu'en y repensant std::tuple est peut-être plus adapté.

    Les deux éléments du pair ne sont qu'une seule et même donnée exprimée sous différentes formes (entendez qu'à partir de la première on peut calculer la deuxième et inversement). C'est donc une information redondante, toutefois cela évite un calcul lourd et fastidieux de conversion à chaque fois qu'on a besoin de manipuler ces données sous une forme ou sous une autre. La première forme sert à faire tous les calculs de collision, d'existence, etc. La 2e sert à l'affichage, et la recréer à chaque frame serait pure perte de performance. La 2e contient d'autres données, toutefois en l'état actuel des choses ces données ne sont pas utilisées/déterminables par calcul depuis la 1e forme.
    Mais en quoi cela t'empêche-t-il d'utiliser une structure personnelle

    Comme je l'ai dit, la std::pair n'est... qu'une abstraction tout à fait générique d'un regroupement de deux éléments de types distincts qui vont bien ensemble.

    Rien ne t'empêche de créer une structure similaire, mais portant un nom plus explicite et fournissant, pourquoi pas, certains comportements susceptibles d'être appelés par l'une ou l'autre données qui la compose

    Comme je l'ai dit lors de ma première intervention, la std::pair est très utile quand elle est utilisée par la std::map, pour la simple et bonne raison que la STL ne sait pas, au moment où elle l'implémente, quel type de donnée sera utilisé comme clé et quel (autre) type de donnée sera utilisé comme valeur.

    Si tu devais travailler avec un paire d'objet "de type variés", dans un contexte générique, l'idéal serait, au minimum, d'avoir un typedef (fusse-t-il imbriqué et pourquoi pas privé) de ta std::pair qui fournisse, au minimum, une indication de l'usage pour lequel la pair est prévue.

    Mais, en sachant exactement quels sont les deux types de données utilisés et, surtout, sans recourir à un typedef, l'utilisation de pair tend vraiment à rendre le code beaucoup plus difficile à comprendre!

    Pour le sf::Vector2f et la dépendance que cela crée avec SFML, je dirais que je prend le parti d'exploiter un framework au maximum.
    Je ne parlais pas de la dépendance avec SFML, mais bien de la dépendance avec std::pair
    Pour ce qui est de s'en servir une seule fois de cette lambda, je dirais que si j'avais à la réutiliser je me contenterais de rajouter une fonction decltype(m_triangles)::iterator findTriangle(const sf::Vector &center) qui contiendrait simplement cette ligne.
    Le problème, c'est que l'on ne dispose ici que d'une seule ligne de code, et que l'on est donc hors de tout contexte

    Comme je l'ai dit, je peux envisager l'utilisation d'une expression lambda dans un contexte bien particulier qui est que ce que l'on a développé et que l'on utilise "par ailleurs" ne fournit pas ce dont on a exactement besoin pour un usage clairement spécifique.

    Mais je crois sincèrement que les lambdas doivent être utilisées avec la plus extrême prudence, et en veillant à garder le code le plus lisible possible.

    Je verrais sans doute cette ligne de code d'un bien meilleur oeil si je savais qu'elle est la seule ligne (avant le return) d'une fonction proche de finXxxByPosition ou quelque chose du genre, car, à défaut de passer un quart d'heure à la déchiffrer je pourrais estimer que le nom de la fonction est suffisamment clair pour m'indiquer le but de cette ligne (que je considérerais d'ailleurs sans doute par défaut comme faisant exactement ce que j'attends d'elle).

    Et je t'avouerai même que si cette fonction findXxxByPosition devait être privée parce que seule(s) une ou deux (allez, mettons "quelques") fonctions membres l'utilis(ent), je ne m'en sentirais que mieux

    Maintenant, je serais sans doute malade de trouver cette ligne de code "enfouie" au milieu d'une vingtaine d'autres dans le corps d'une fonction complexe

    Et je deviendrais enragé si je devais me rendre compte qu'elle se répète dans trois ou quatre fonctions membres différentes

    Peut etre n'ai-je tout simplement pas encore assez l'habitude de croiser des expressions lambda dans le code, et, si cela se trouve, je relirai cette intervention dans deux ans en me disant "mais qu'ai-je été écrire comme connerie"...

    Mais je ne peux m'empêcher de penser, à la vue d'une seule ligne de code totalement hors contexte toujours, qu'il me semble que tu dénature quelque peu l'usage pour lequel les expressions lambda ont été mise au point (tout comme je pense que tu dénature la raison d'être de la std::pair )
    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

  11. #11
    Membre Expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Par défaut
    Je ne parlais pas de la dépendance avec SFML, mais bien de la dépendance avec std::pair
    gbdivers si.

    Pour préciser le contexte, toutes les manipulations relatives à m_triangles sont absolument encapsulées et de plus découpées en un grand nombre de fonction constantes (tout est privé, les 2 seules fonctions publiques de la classe n'exposent absolument rien de l'implémentation ni même des types utilisés en interne).

    Je suis adepte du DNRY, moins je recopie mieux je me porte. Donc tu ne trouveras pas deux fois le même bout de code dans mon code. Je ne crée une abstraction qu'uniquement si je dois utiliser quelque chose 2 fois (ou plus). Dans le cas contraire, un type standard me suffit amplement.

    Pour ce qui est des lambdas, un code avec lambda est maintenant pour moi bien plus lisible que l'équivalent sans lambda.

    Je suis également convaincu par la documentation par le nommage correct. Mais documenter une fonctionnalité purement interne en payant un surcout de temps de développement et de lisibilité, je dis non.

    Je verrais sans doute cette ligne de code d'un bien meilleur oeil si je savais qu'elle est la seule ligne (avant le return) d'une fonction proche de finXxxByPosition ou quelque chose du genre, car, à défaut de passer un quart d'heure à la déchiffrer je pourrais estimer que le nom de la fonction est suffisamment clair pour m'indiquer le but de cette ligne (que je considérerais d'ailleurs sans doute par défaut comme faisant exactement ce que j'attends d'elle).
    Et qu'est donc supposé faire la fonction findTriangle dont j'ai parlé ?

  12. #12
    Membre actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2011
    Messages
    54
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Service public

    Informations forums :
    Inscription : Décembre 2011
    Messages : 54
    Par défaut
    TLDR (survolé toutes les réponses en tout cas)

    J'apporte ma modeste contribution pour faire part de mon expérience:
    - de 9 ans dans un studio de dev de jeux vidéo (consoles de salon/PC) dans une équipe de programmeurs de 20 à 30 personnes
    - et un peu plus d'un an dans un service informatique où je suis tout seul à bosser sur un projet

    Malgré les besoins en terme de performance et place mémoire que requièrent les consoles (surtout à l'époque de la PS2/Wii/XBox), le code étant partagé entre plusieurs personnes, et souvent repris plusieurs années plus tard (le moteur de jeu a été développé/maintenu/amélioré pendant environ 8 ans), la clarté du code est extrêmement importante. Et quand je parle de clarté, c'est au sens très large du terme. C'est-à-dire qu'un calcul qui exécute plusieurs opérations et utilise des valeurs récupérées par un appel de fonction sur une seule ligne doit être découpé étape par étape. Ceci a un autre but: le debuggage plus simple et plus rapide. Quand quelqu'un repasse 3 ans plus tard sur un bout de code du moteur, on voit directement les valeurs utilisées dans le calcul, on peut breaker sur l'appel de fonctions que l'on veut, etc. C'est un gain de temps et de productivité, et moins de chance d'avoir un bug que quand on écrit une ligne de code de 200 caractères.
    Bien entendu, commenté le code était fortement recommandé! Il fallait toujours garder à l'esprit que notre code serait repris/relu/corrigé par quelqu'un d'autre. C'est con, mais quand on crache du code à longueur de journée, même son propre code peut être difficile à relire quelques mois plus tard.
    Bref, quand on bosse en équipe, le respect du guide de style est très important. Combien de fois a-t-on entendu "Raaah, mais pu**in, qui a indenté son code comme ça?" résonner dans l'open space

    Aujourd'hui que je travaille seul sur un projet, j'ai quand même gardé ces pratiques. C'est un peu de l'auto-discipline, car personne ne viendra vérifier la qualité du code que j'écris. Je pourrais faire les choses les plus sales au monde en C++, il n'y a que moi que ça regarde. Et j'aime un code qui se comprend à la première lecture.

    TLDR (celui-là c'est pour ceux qui lisent mon message): Personnellement, le code doit être le plus clair possible. Si une ligne est "complexe" (et que c'est justifié), je la précède en général d'un gros commentaire de plusieurs lignes. Donc le premier bout de code sur 2 lignes, pourquoi pas, s'il est précédé d'un truc du genre (si j'ai bien compris le code):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    // un triangle est défini par un couple (Centre; Forme). le tout rangé dans une liste
    std::list<std::pair<sf::Vector2f, sf::ConvexShape>> m_triangles;
    // récupère un itérateur sur un triangle pour un centre qui correspond une position donnée
    auto it = std::find_if(begin(m_triangles), end(m_triangles), [pos](const std::pair<sf::Vector2f, sf::ConvexShape> &p){return p.first == pos;});

    My 2 cents

  13. #13
    Membre Expert
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Par défaut
    (j'aurais d'ailleurs bien aimé une map dont je puisse décider du value_type et du membre qui constitue la clef).
    C’est aussi une structure de données qui me manque très régulièrement, quelque soit le langage utilisé.

    Par contre, je suis dubitatif sur la possibilité de la réaliser correctement en c++. Avec des macros, on arrive à quelque chose, mais c’est loin d’être élégant à l’usage.

  14. #14
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    Je suis également convaincu par la documentation par le nommage correct. Mais documenter une fonctionnalité purement interne en payant un surcout de temps de développement et de lisibilité, je dis non.
    Où sont la perte de lisibilité et le cout de développement occasionnés par le fait d'écrire une fonction dont le nom indique explicitement ce qui est fait, de deux lignes qui ne fait qu'une chose, mais qui la fait bien

    Au pire, on peut considérer que tu perds peut etre un peu de temps à l'exécution du fait de l'appel de la fonction, mais il reste malgré tout minime

    Et je dois préciser que je ne me basais, pour mon intervention, que sur le code présenté par gbdivers
    Et qu'est donc supposé faire la fonction findTriangle dont j'ai parlé ?
    C'est en fait d'elle que je parlais...

    Mais, comme je l'ai (me semble-t-il) clairement signalé dans mon intervention, la ligne de code présentée est totalement hors contexte.

    Il nous est donc pour le moins difficile de déterminer si cette ligne n'est que la première d'une fonction "bien nommée" qui ne fait que cela ou si c'est au contraire une ligne "perdue au milieu d'une vingtaine d'autres" dans une fonction beaucoup plus complexe.

    A mon avis, si tu te trouves (et je n'ai jamais dit que c'était le cas, je pars juste d'une hypothèse "pessimiste" ) dans la deuxième situation, tu aurais largement intérêt à envisager la création de cette fonction particulière bien nommée, et ce, même si elle ne doit être appelée qu'une seule et unique fois dans ton code

    [EDIT]Au passage, tu remarqueras que mon intervention utilise presque exclusivement le conditionnel, et que j'ai pris soin de préciser les différentes situations qui pourraient occasionner différentes réactions de ma part...

    En résumé, on pourrait dire:
    • Dans une fonction bien nommée qui ne fait que ca, cela ne me pose aucun problème
    • Planquée au milieu du code d'une fonction complexe, c'est franchement limite
    • Planquée au milieu du code de plusieurs fonctions complexes: tu risquerais de passer par la fenêtre
    Mais comme on ne sait absolument pas quel est le contexte actuel dans lequel cette ligne de code apparait, il est difficile de savoir s'il faut te donner notre bénédiction, te conseiller vivement de créer une fonction particulière ou te pendre haut et court
    [/ EDIT]
    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

  15. #15
    Membre Expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Par défaut
    une fonction d'une vingtaine de lignes de ce genre serait une fonction bien mal découpée...

  16. #16
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    une fonction d'une vingtaine de lignes de ce genre serait une fonction bien mal découpée...
    Nous sommes bien d'accord là dessus

    A ceci près toute fois qu'elle le serait selon moi bien avant d'atteindre la vingtaine de lignes

    C'est, encore une fois, la raison pour laquelle je ne peux que t'inciter (si ce n'est pas encore fait) à créer ta fonction findTriangle même si elle ne doit être invoquée que depuis une seule autre fonction

    Tout comme je ne saurais trop de conseiller de définir (si ce n'est pas encore fait) au minimum un typedef sur ta paire, "simplement" parce que, sans rien couter, ce serait très certainement de nature à permettre une lecture et une compréhension beaucoup plus aisée

    Encore une fois, tu remarqueras que ce n'est en substance que le message que j'ai essayé de faire passer tout au long de mes interventions

    N'oublies pas, en outre, que les typedefs respectent la visibilité dans laquelle ils sont définis...

    L'idéal étant, sans doute, de définir celui-ci dans un accessibilité privée, vu que, d'après ce que je crois avoir compris, nous sommes dans le cadre de la seule classe qui regroupe ces deux types de valeurs
    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

  17. #17
    Membre Expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Par défaut
    Pour le using (typedef c'est dépassé), je vais certainement le mettre. Tout comme la fonction findTriangle. Mais en ce moment j'essaye de suivre une règle pour voir si c'est efficace : je code d'abord quelque chose, et seulement si je dois le réutiliser alors je fais une abstraction.

  18. #18
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    typedef c'est dépassé
    C'est pas la première fois que tu fais cette remarque... c'est sérieux ou j'ai loupé quelque chose ? Ou alors, c'est dans un contexte particulier ?

  19. #19
    Membre Expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Par défaut
    Oui c'est sérieux, à moins que tu ne me trouve un endroit où c'est absolument nécessaire... Dans tous les cas que je connais le using est toujours possible...

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    A mon avis, cette règle (outre le fait qu'elle contrevient, plus ou moins, au principe de la responsabilité unique) ne peut être efficace que pour le "très court terme", pour un "POC" (Proof Of Concept)...

    Cela mérite sans doute une explication, non , la voici

    Mettons que tu te poses la question de "comment faire un truc particulier".

    Au moment où tu te poses la question, les objectifs et les besoins sont (du moins, on va les considérer comme tels ) tout à fait clair dans ton esprit.

    Tu vas donc commencer ton code en ayant à l'esprit tous les objectifs, les besoins, les problèmes éventuels à surmonter, et, surtout, le code que tu viens d'écrire.

    Tant que tu seras dans cette phase d'écriture du code, il n'y aura pas de problème: il sera très facile de te souvenir des lignes que tu as écrit il y a dix minutes et des raisons qui ont fait que tu les as écrites.

    Tu remarqueras donc "assez facilement" si tu en viens à utiliser plusieurs fois une même logique ou une même structure, et tu pourras, tout aussi facilement, décider de créer l'abstraction supplémentaire en cas de besoin (je compte, ici, le fait de factoriser quelques lignes de code dans une fonction séparée comme étant une abstraction supplémentaire, même si ce n'est pas tout à fait juste ) .

    Le problème surviendra une fois passé le "moment présent", si, pour une raison ou une autre, tu en viens à devoir modifier ton code ou ajouter des fonctionnalités dans, mettons, un mois ou deux.

    Dans le meilleur des cas, tu te souviendras "plus ou moins" de manière générale ce que font les différentes fonctions, mais tu auras sans doute oublié que, dans telle fonction particulière, tu as écrit deux, trois, dix lignes qui correspondent à une étape bien particulière de la fonction.

    Pas de bol, la loi de murphy aidant, tu peux être quasiment certain que les modifications que tu devras apporter nécessiteront, justement, d'écrire ces deux, trois ou dix lignes, parce que la fonction sur laquelle tu travailles a, justement, besoin de cette étape particulière

    Et comme tu n'auras pas fait l'abstraction supplémentaire au moment où son besoin est apparu (c'est à dire, dés la première fois où tu as eu besoin de ces quelques lignes de code correspondant à une étape particulière), ou bien, tu recopieras le code (adieu, DRY ), ou bien, tu perdras un temps bête à essayer de te souvenir de "mais bon dieu, dans quelle fonction est-ce que j'avais besoin de ce satané bout de code ?", avant même d'arriver à le situer dans cette fonction afin d'en créer une abstraction.

    Le problème est d'autant plus important si tu travailles en équipe, car le code que tu dois revoir n'a peut etre simplement pas été écrit par toi à la base, et que tu vas donc, régulièrement, te baser sur le nom des fonctions dont tu disposes, et considérer "qu'elles font ce qu'on attend d'elles"...

    Comme tu ne trouveras aucune fonction dont le nom te laisse à penser qu'elle fournit une étape particulière, tu en viendras, naturellement, à penser qu'aucune des fonctions complexes auxquelles tu as accès n'en ont eu besoin.

    AMHA, cette règle de ne créer une abstraction supplémentaire ne peut donc réellement s'appliquer que sur du très court terme.

    Je dirais que, si tu peux l'appliquer, c'est au grand maximum sur une durée de deux heures, au delà, il me semble bon que toutes les abstractions possibles aient été faites, et, au plus tard, au moment où tu décideras de commiter les changements sur ton gestionnaire de version
    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. Perte d'enregistrements
    Par AnnSo dans le forum Paradox
    Réponses: 15
    Dernier message: 06/08/2006, 23h39
  2. [ALGO] dessiner un triangle dans le bon sens
    Par lefait dans le forum Algorithmes et structures de données
    Réponses: 13
    Dernier message: 05/02/2005, 14h38
  3. Perte de connexion (bis)
    Par rgarnier dans le forum XMLRAD
    Réponses: 7
    Dernier message: 28/05/2003, 11h14
  4. Perte du contenu des blobs
    Par macakou99 dans le forum Débuter
    Réponses: 10
    Dernier message: 22/05/2003, 15h17
  5. [UDP][Socket] perte de paquets et arret d'ecoute sur port
    Par Guismo1979 dans le forum Développement
    Réponses: 6
    Dernier message: 02/01/2003, 12h13

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