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 :

Conteneur et habitudes


Sujet :

C++

  1. #1
    Membre éclairé
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Points : 719
    Points
    719
    Par défaut Conteneur et habitudes
    Bonjour !

    Ce que je trouve le plus difficile quand on apprend un langage, ce n'est pas nécessairement la syntaxe ou les problèmes dans lesquels tout le monde tombe, mais plutôt d'acquérir les pratiques et habitudes de ceux qui ont de l'expérience dans le langage en question. Mon cas aujourd'hui concerne justement une de ces "habitudes" qu'un développeur C++ expérimenté a probablement.

    Supposons que j'écrive une fonction censée retourner une liste de fichiers (par exemple les photos présentes sur un périphériques). Pour moi, programmeur c++ inexpérimenté, un prototype naturel serait celui-ci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    void GetPhotoList (const std::string& device_name, std::vector<std::string>& picture_list);
    Déjà j'ai choisi un vecteur de strings plutôt qu'un std::list parce que cette liste ne sera généralement pas modifiée par l'appelant. C'est peut-être pas le bon choix ?

    Ensuite, ce que je n'aime pas dans ce prototype c'est que l'appelant n'a pas la moindre idée de ce que le précédent contenu de mon vecteur va devenir. La fonction va t elle ajouter la liste à la fin ou va t-elle remplacer le contenu existant ?

    Alors je me suis dit que je pourrais simplement fournir un template qui prend un iterateur "destination" et laisser l'appelant se débrouiller avec ça. Mon prototype deviendrait donc grosso modo :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    template<typename It> void GetPhotoList (const std::string& device_name, It dest);
     
    // Code appelant :
    GetPhotoList ("phone", std::back_inserter(mon_conteneur));
    J'aime bien, mais voilà ce que j'y reproche :
    1) L'implémentation de mon GetPhotoList() va se retrouver dans un header, ce qui me pose quelques problèmes pour encapsuler son fonctionnement (il varie d'une plateforme à l'autre).
    2) J'ai pas l'impression de voir ça "souvent" : la majeure partie du code C++ que je rencontre dans ma vie de tous les jours correspond plutôt à mon premier exemple (dois-je trouver des collègues plus expérimentés ? ).
    3) Dans 99% des cas, je vais utiliser cette fonction avec un vecteur. Pourquoi pas simplifier ce cas général et laisser l'appelant se débrouiller à convertir ensuite vers un autre conteneur pour les 1% restants ?

    Voilà où j'en suis, j'aimerais beaucoup des opinions et recommendations pour me faire une idée, non seulement pour ce cas, mais pour mes développements futurs. Merci beaucoup.

  2. #2
    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
    Salut,

    A vrai dire, le choix d'utiliser le std::vector est sans doute le "choix par défaut" de bien de développeurs, simplement parce qu'il combine énormément d'avantages (dont celui de présenter un accès en temps constant).

    Mais il combine également énormément d'inconvénients, parmi lesquelles on peut citer (de manière non exhaustive):
    • Le déplacement des éléments en mémoire lors de l'ajout
    • l'impossibilité d'effectuer une recherche dichotomique (en log(n) ) sans s'être au préalable assuré qu'il était trié
    • La nécessité de disposer d'un espace contigu en mémoire suffisant pour pouvoir y placer l'ensemble des éléments qu'il contient
    • ...
    Dans de très nombreuses circonstances, ces inconvénients forcent le développeur à se tourner vers un type de collection différent qui permette d'assurer "la bonne marche" d'un point précis de l'application.

    Penses, par exemple, au problème énoncé dans cette discussion, et tu verras que l'utilisation d'un vecteur est très loin d'être sans conséquence, y compris pour les classes ayant sémantique de valeur.

    Ceci dit, pour répondre clairement à ta question:

    1- C'est à toi que revient la responsabilité de savoir, en fonction du projet sur lequel tu travailles, s'il utilise toujours un std::vector ou s'il lui arrive d'utiliser autre chose.

    2- C'est à toi de définir clairement le comportement que tu attends de la part de ta fonction : doit-elle (ou non) vider la collection avant d'y ajouter les nouveaux éléments Peut être peux tu envisager de rajouter un booléen permettant de choisir entre vider ou non la collection

    Quoi qu'il en soit, tu dois veiller à ce que le comportement soit explicite pour l'utilisateur de la fonction, en précisant ce fait au niveau du cartouche.

    3- pour répondre spécifiquement à ta phrase
    1) L'implémentation de mon GetPhotoList() va se retrouver dans un header, ce qui me pose quelques problèmes pour encapsuler son fonctionnement (il varie d'une plateforme à l'autre).
    je dirais qu'il faut donc veiller à utiliser une abstraction au niveau de GetPhotoList pour obtenir la liste des fichier. Rappelle toi la phrase de David Wheller
    Citation Envoyé par David Wheller
    all problems in computer science can be solved by another level of indirection
    (Tout problème en informatique peut être résolu par un autre niveau d'indirection)
    4 - Rien ne t'empêche de prévoir deux fonctions si tu te rends compte, dans ton projet particulier, que la liste des fichiers prend parfois la forme d'autres collections qu'un std::vector : la première, template, qui se contente de rajouter les éléments sur base d'un itérateur, la seconde, spécifique à std::vector, qui appellerait la première.

    Cela pourrait prendre une forme sans doute très proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<typename It> void GetPhotoList (const std::string& device_name, It dest){
        FileHolder holder(device_name); //l'abstraction dont je te parlais la tantot
        std::copy(holder.begin(), holder.end(), dest);
    }
    void getPhotoList(std::string const & device_name, std::vector<std::string> & vector, bool clearList /* = true */){
        if(clearList)
            vector.clear();
        GetPhotoList ("phone", std::back_inserter(vector));
    }
    Le tout dépendant, bien évidemment, de la situation dans laquelle tu te trouves par rapport à ton projet
    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

  3. #3
    Expert éminent sénior

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 045
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Software Developer
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 045
    Points : 11 368
    Points
    11 368
    Billets dans le blog
    10
    Par défaut
    Tu peux aussi voir ta fonction de manière différente :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::vector<std::string> GetPhotoList( const std::string& device_name );
    Ca règle au moins le problème de vider ou pas le conteneur en entrée.
    Si vous ne trouvez plus rien, cherchez autre chose...

    Vous trouverez ici des tutoriels OpenGL moderne.
    Mon moteur 3D: Castor 3D, presque utilisable (venez participer, il y a de la place)!
    Un projet qui ne sert à rien, mais qu'il est joli (des fois) : ProceduralGenerator (Génération procédurale d'images, et post-processing).

  4. #4
    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 dragonjoker59 Voir le message
    Tu peux aussi voir ta fonction de manière différente :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::vector<std::string> GetPhotoList( const std::string& device_name );
    Ca règle au moins le problème de vider ou pas le conteneur en entrée.
    Mais cela en pose d'autres :

    - Tu forces l'utilisation d'une collection particulière, ce qui peut (ou non) être une contrainte forte qui peut (ou non) impliquer la nécessité de copier le contenu dans une autre collection, plus adaptée à une situation particulière (un std::set, par exemple).

    Et la copie prend du temps

    - Tu places l'utilisateur face à la nécessité de choisir ce qu'il fait du std::vector récupéré : Doit-il utiliser cette collection "telle quelle", sans s'inquiéter du reste ou doit-il commencer par en rajouter le contenu dans une autre collection

    Il serait dommage de forcer l'utilisateur à "refaire" ce que la fonction a déjà fait parce qu'il se trouve dans une situation dans laquelle le contenu du std::vector aurait du être ajouté à celui d'un autre, tu ne crois pas

    Je dois cependant, par honnêteté intellectuelle, accorder un avantage certain à cette manière de faire : on évite les effets de bord que le passage de std::vector par référence occasionne.

    On pourra donc envisager beaucoup plus sereinement de l'utiliser dans un contexte multi thread ou sous une forme plus ou moins récursive

    Voici encore une fois la preuve que tout a un prix en informatique
    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
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 704
    Points
    2 704
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Et la copie prend du temps
    Mais y a-t-il vraiment copie ?
    Bien souvent, le compilateur fait en sorte que non.

  6. #6
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Salut

    Citation Envoyé par koala01 Voir le message
    Et la copie prend du temps
    D'une part le compilateur va optimiser mais d'autre part, avec C++11, la move semantics sera utilisée et on est certain qu'il n'y aura pas de copie du tout (à moins d'avoir implémenté la méthode comme un pied et qu'il y ai une copie en interne, mais c'est une autre histoire).

    Citation Envoyé par koala01 Voir le message
    Tu forces l'utilisation d'une collection particulière, ce qui peut (ou non) être une contrainte forte qui peut (ou non)
    Il est quand même rare que ce soit vraiment le cas dans la pratique. S'il se présente, alors on choisit une autre solution (passage par référence avec plusieurs fonctions, templates avec itérateurs).

    Avec C++14 et l'inférence par type de retour, on pourra même écrire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::vector<std::string> GetPhotoList( const std::string& device_name );
    std::list<std::string> GetPhotoList( const std::string& device_name );
    Find me on github

  7. #7
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Citation Envoyé par koala01 Voir le message
    - Tu places l'utilisateur face à la nécessité de choisir ce qu'il fait du std::vector récupéré : Doit-il utiliser cette collection "telle quelle", sans s'inquiéter du reste ou doit-il commencer par en rajouter le contenu dans une autre collection
    Je ne comprends pas ce passage. En quoi c'est négatif que l'utilisateur ait le choix de la manière dont il va utiliser le std::vector récupéré ?

  8. #8
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Puisque la question originale concerne les pratiques et habitudes, la mienne, dans ce type de situation, consiste à utiliser la première méthode proposée:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void GetPhotoList (const std::string& device_name, std::vector<std::string>& picture_list);
    Avec 2 remarques:
    - Généralement, je retourne un booléen qui permet de tester, si besoin est, que tout s'est bien passé. Pour certaines choses simples, je préfère un bête test plutôt que la gestion d'exceptions*.
    - Dans ce cas de figure, je réinitialise systématique le conteneur (ici picture_list) au début de la fonction, généralement en utilisant un swap trick.

    Je ne dis pas que c'est la meilleure façon de faire, je n'en sais rien, mais c'est une habitude que j'ai pris et cette méthode répond raisonnablement à la plupart des problèmes qui peuvent se poser. Notamment sur la question de l'optimisation (copie ou pas), là on est sûr qu'il n'y a pas de copie, la question ne se pose pas.


    * Dans le cas présent, qui est un contre-exemple de ce que je dis, il y a accès à une ressource donc l'utilisation d'exception sera certainement le plus approprié.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  9. #9
    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
    Je m'absente deux petites heures, et je retrouves un tas de réaction à mes écrits dites donc
    Citation Envoyé par oodini Voir le message
    Mais y a-t-il vraiment copie ?
    Bien souvent, le compilateur fait en sorte que non.
    Il est effectivement plus que probable que la sémantique de mouvement soit utilisée... Mais ce n'est pas forcément gratuit pour autant
    Citation Envoyé par jblecanard Voir le message
    D'une part le compilateur va optimiser mais d'autre part, avec C++11, la move semantics sera utilisée et on est certain qu'il n'y aura pas de copie du tout (à moins d'avoir implémenté la méthode comme un pied et qu'il y ai une copie en interne, mais c'est une autre histoire).
    Même la sémantique de déplacement n'est pas gratuite et prend du temps.

    Moins que la copie, je te l'accorde, mais je te rappelle qu'on part du principe d'un std::vector qui a de fortes chances de devoir effectuer un resize pour rajouter le contenu du vecteur obtenu par la fonction à celui qu'il s'agit de "compléter"
    Citation Envoyé par Arzar Voir le message
    Je ne comprends pas ce passage. En quoi c'est négatif que l'utilisateur ait le choix de la manière dont il va utiliser le std::vector récupéré ?
    Primo, parce que cela oblige le développeur à refaire, en cas de besoin, en dehors de la fonction exactement ce que fait (ou du moins une bonne partie de ce que fait ) la fonction... Avoues que c'est un peu dommage, non

    Secundo parce qu'il est tout à fait possible de laisser le choix à l'utilisateur de la fonction, tout en faisant en sorte que la "vidange" de la collection se fasse de manière transparente pour l'utilisateur.

    Cela te permet de définir beaucoup plus finement les responsabilités de chacune de tes fonctions -- celle de GetPhotoList devenant de mettre à jour la liste des photos, quel que soit le sens donné à "mettre à jour" -- et donc de respecter d'avantage le principe de la responsabilité unique
    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

  10. #10
    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
    @koala01> Il y aura probablement élision du/des copy/move-ctor, donc le coût est probablement nul en réalité. (Je suis d'accord avec ton autre argument par contre, même si ça peut dépendre de la situation)

  11. #11
    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 Flob90 Voir le message
    @koala01> Il y aura probablement élision du/des copy/move-ctor, donc le coût est probablement nul en réalité. (Je suis d'accord avec ton autre argument par contre, même si ça peut dépendre de la situation)
    Cet argument est peu probant à mon sens:

    Tu auras, effectivement, élision de la copie (enfin, on va plutôt parler d'extension de la durée de vie) si l'utilisateur récupère le tableau et le manipule "tel quel" sans plus rien y toucher.

    Face à un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void foo(){
       std::vector<std::string> tab(GetPhotoList(device_name ));
       /* Ce pourrait être 
       std::vector<std::string> tab = GetPhotoList(device_name );
       for(auto it : tab){
           /* manipulations diverses et variées */
       }
    }
    tu profiteras de toutes les élisions possibles, sans le moindre problème.

    Mais dés le moment où l'utilisateur envisage de rajouter le contenu du tableau renvoyé par la fonction dans une autre collection, par exemple, face à un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void bar(std::list<std::string> const & devices){
        std::vector<std::string> allPhotos;
        for(it : devices){
            std::tab<std::string> recup(GetPhotoList(*it ));
            /* C'est bizare, mais cette dernière ligne me fait furieusement penser
             * à quelque chose que j'ai déjà croisé !!!!
             */
            std::copy(recup.begin(), recup.end(),std::back_inserter(allPhotos));
        }
    }
    les élisions dont tu pourras profiter n'empêcheront nullement que le compilateur devra copier chacune des chaines de caractères que tab peut contenir.

    Il peut éventuellement (mais je suis-je pas en train de donner au compilateur plus "d'intelligence" qu'il n'en a ) se rendre compte que tab est une variable locale et utiliser la sémantique de mouvement plutôt que la copie, mais il devra, de toutes manières, insérer tous les éléments de tab dans allPhotos, et ca, ca aura de toutes manières un prix, quel que soit la manière dont il pourra s'y prendre
    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

  12. #12
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par phi1981 Voir le message
    Alors je me suis dit que je pourrais simplement fournir un template qui prend un iterateur "destination" et laisser l'appelant se débrouiller avec ça. Mon prototype deviendrait donc grosso modo :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    template<typename It> void GetPhotoList (const std::string& device_name, It dest);
     
    // Code appelant :
    GetPhotoList ("phone", std::back_inserter(mon_conteneur));
    Hello,

    Bien que partant d'une bonne idée, je n'aime pas du tout.
    Il est ici impossible de déterminer le nombre d'éléments ajouté au conteneur par la fonction, il faut donc toujours utiliser un inserter. Et un oubli est vite arrivé (le back_inserter est construit dans le code appelant).

    Pour quelques chose de générique je verrais plutôt
    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
    // exemple pour un vector, il est bien sur possible de rendre ça template pour avoir une version qui marche pour vectors et lists par exemple
    // il est aussi possible d'ajouter au milieu d'un conteneur etc.. (et dans ce cas l'inserter est construit dans l'adapter,
    // pas lors de l'appel de la fonction -> moins de possibilité d'oublis.)
    struct VectorAdapter {
    	void add(std::vector<std::string>& vec, const string& str) const {
    		vec.push_back(str);
    	}
    	// ...
    };
     
    template <class Contener, class Adapter>
    void GetPhotoList(const std::string& device_name, Contener& c, const Adapter& adapter = VectorAdapter()) {
    	// ...
    	adapter.add(c, "some value");
    	// ...
    }
    Ceci dit, quand il n'y à pas vraiment de raisons de généricité (= je n'en ai pas besoin pour le moment), je ferai
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void GetPhotoList(const std::string& device_name, std::vector<std::string>& photoList);
    // ou
    std::vector<std::string> GetPhotoList(const std::string& device_name);
    Avec une préférence pour la deuxième forme, sauf si le but est d'ajouter des valeurs à un conteneur non vide.

  13. #13
    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
    @koala01> Oui, je suis d'accord, mais à mon sens ce que tu viens de développer c'est ton second argument (avec lequel j'étais d'accord) et pas le premier

  14. #14
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Yo

    Citation Envoyé par koala01 Voir le message
    Même la sémantique de déplacement n'est pas gratuite et prend du temps.
    Je trouve que c'est un peu un argument de mauvaise foi dans la mesure où ce coût est de très loin négligeable par rapport à une copie. Si on en vient à ce genre de détails, c'est de la micro-optimisation, et ça les compilateurs le font bien mieux que les développeurs. On peut toujours trouver des cas tordu, mais ça reste vrai presque tout le temps.

    Si tu veux "éviter" le coût d'une sémantique de déplacement, tu vas complexifier ton code (rajouter des risques d'erreurs, maintenance plus difficile) pour gagner des cacahouètes, ou même probablement rien vu ce que font les compilos moderne. Honnêtement, la plupart des devs sont loin de se rendre compte à quel point les optimisations compilo sont plus malines qu'eux. Ne serais-ce que d'un point de vue ego, ce n'est pas toujours facile d'admettre qu'une machine fera mieux, et pourtant, en ce qui concerne la compile, c'est quasiment toujours le cas. Il y a une quantité énorme de devs qui se croient malins en écrivant du code "optimisé" alors qu'en fait, ça ne sert juste qu'à le rendre moins lisible

    Citation Envoyé par koala01 Voir le message
    Moins que la copie, je te l'accorde, mais je te rappelle qu'on part du principe d'un std::vector qui a de fortes chances de devoir effectuer un resize pour rajouter le contenu du vecteur obtenu par la fonction à celui qu'il s'agit de "compléter"
    D'accord mais cet état de fait est complètement indépendant de la manière de récupérer le vector non ? Je ne vois pas trop le rapport avec le débat sur la copie/mouvement. Même si on passe le vector a remplir par référence, le problème se pose quand même.

    D'une manière générale, pour ce genre de problème, la première règle à respecter est d'écrire le code le plus simple et le plus limpide possible. Ce qui va vous coûter cher, c'est le réseau, les accès disques, les grosses allocation de mémoire... Si on prend le cas de notre op ici, qui récupère une liste de fichier sur un device, le coup de copie d'un vector est de très très très loin négligeable par rapport au coût de lecture du dit device.
    Find me on github

  15. #15
    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 Flob90 Voir le message
    @koala01> Oui, je suis d'accord, mais à mon sens ce que tu viens de développer c'est ton second argument (avec lequel j'étais d'accord) et pas le premier
    Le fait est que les deux arguments sont intimement liés...

    Les élisions diverses et variées ne seront en mesure de n'apporter un gain perceptible et certes sans doute intéressant que dans une situation donnée et bien particulière.

    Dans toute autre situation, cette élision n'aura strictement aucun effet, alors que le fait de transmettre la collection par référence offrira un gain équivalent à l'élision dans n'importe quelle situation.

    C'est dans cet ordre d'esprit que je trouve l'argument de l'élision peu convaincant
    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

  16. #16
    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 jblecanard Voir le message
    Je trouve que c'est un peu un argument de mauvaise foi dans la mesure où ce coût est de très loin négligeable par rapport à une copie. Si on en vient à ce genre de détails, c'est de la micro-optimisation, et ça les compilateurs le font bien mieux que les développeurs. On peut toujours trouver des cas tordu, mais ça reste vrai presque tout le temps.

    Si tu veux "éviter" le coût d'une sémantique de déplacement, tu vas complexifier ton code (rajouter des risques d'erreurs, maintenance plus difficile) pour gagner des cacahouètes, ou même probablement rien vu ce que font les compilos moderne. Honnêtement, la plupart des devs sont loin de se rendre compte à quel point les optimisations compilo sont plus malines qu'eux. Ne serais-ce que d'un point de vue ego, ce n'est pas toujours facile d'admettre qu'une machine fera mieux, et pourtant, en ce qui concerne la compile, c'est quasiment toujours le cas. Il y a une quantité énorme de devs qui se croient malins en écrivant du code "optimisé" alors qu'en fait, ça ne sert juste qu'à le rendre moins lisible


    D'accord mais cet état de fait est complètement indépendant de la manière de récupérer le vector non ? Je ne vois pas trop le rapport avec le débat sur la copie/mouvement.
    N'inversons pas les rôles, veux tu bien

    C'est toi qui est venu mettre la sémantique de déplacement sur le tapis!

    Si j'ai argumenté sur ce point particulier, c'était dans le cadre d'une réflexion globale, dont la partie (bien qu'elle citait Azar) que tu as zappée était le pendant!

    Tu arrivais avec ton ton péremptoire en disant que, de toutes manières, la sémantique de mouvement c'était pas pour les chiens, et laissant presque penser que c'est une opération " zero cost ".

    Je me devais donc d'insister sur le fait que, non, décidément, la sémantique de déplacement n'est pas " zero cost ", et j'ai déjà fait preuve de beaucoup plus d'honnêteté intellectuelle que toi en admettant sans aucun problème le fait que la sémantique de déplacement a un cout largement inférieur à la copie pure et simple!

    Mais c'est aussi le mieux que je pouvait t'accorder, en toute honnêteté intellectuelle
    D'une manière générale, pour ce genre de problème, la première règle à respecter est d'écrire le code le plus simple et le plus limpide possible. Ce qui va vous coûter cher, c'est le réseau, les accès disques, les grosses allocation de mémoire... Si on prend le cas de notre op ici, qui récupère une liste de fichier sur un device, le coup de copie d'un vector est de très très très loin négligeable par rapport au coût de lecture du dit device.
    Je n'en disconvient absolument pas!

    Mais il n'empêche que, sans chercher à faire de la micro optimisation, le simple fait de transmettre la collection par référence en tant qu'argument pour une fonction:
    1. ne rend certainement pas le code plus compliqué ( par rapport à une variable déclarée dans la fonction)
    2. supprime tout besoin de copie et ou de sémantique de mouvement
    3. Permet de dégager l'utilisateur d'un responsabilité en la prenant en charge de manière transparente, et ce, à moindre cout (un booléen suffit)
    En comparaison avec les inconvénients de la technique en termes d'effets de bord, je trouve malgré tout que c'est un compromis des plus acceptables
    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
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Il peut éventuellement (mais je suis-je pas en train de donner au compilateur plus "d'intelligence" qu'il n'en a ) se rendre compte que tab est une variable locale et utiliser la sémantique de mouvement plutôt que la copie, mais il devra, de toutes manières, insérer tous les éléments de tab dans allPhotos, et ca, ca aura de toutes manières un prix, quel que soit la manière dont il pourra s'y prendre
    Parce que j'aime les tests
    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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #include <chrono>
    #include <iostream>
    #include <vector>
    #include <iterator>
     
    const unsigned SIZE = 1000;
    const unsigned DEVICES = 100;
    const unsigned TESTS = 1000;
     
    inline std::string getOne() {
    	return "dsjqdjqskdqsjkdjklqsdjkqsdjqsdklqsjkdqsdqsjklmdjqsdjqs";
    }
     
    void getList(std::vector<std::string>& list) {
    	for(unsigned i=0; i<SIZE; ++i) {
    		list.push_back(getOne());
    	}
    }
     
    std::vector<std::string> getList() {
    	std::vector<std::string> ret;
    	for(unsigned i=0; i<SIZE; ++i) {
    		ret.push_back(getOne());
    	}
    	return ret;
    }
     
    void getAll() {
    	std::vector<std::string> all;
    	for(unsigned i=0; i<DEVICES; ++i) {
    		getList(all);
    	}
    }
     
    void getAllMove() {
    	std::vector<std::string> all;
    	for(unsigned i=0; i<DEVICES; ++i) {
    		std::vector<std::string> recup(getList());
    		std::copy(recup.begin(), recup.end(), std::back_inserter(all));
    	}
    }
     
    int main() {
    	std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    	std::chrono::duration<double> elapsed;
     
    	start = std::chrono::high_resolution_clock::now();
    	for(unsigned i=0; i<TESTS; ++i) {
    		getAll();
    	}
    	end = std::chrono::high_resolution_clock::now();
    	elapsed = end - start;
    	std::cout << "getAll: " << elapsed.count() << "s" << std::endl;
     
    	start = std::chrono::high_resolution_clock::now();
    	for(unsigned i=0; i<TESTS; ++i) {
    		getAllMove();
    	}
    	end = std::chrono::high_resolution_clock::now();
    	elapsed = end - start;
    	std::cout << "getAllMove: " << elapsed.count() << "s" << std::endl;
     
    	return 0;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    g++ -std=c++11 -o main main.cpp -O3
    ./main
    getAll: 21.8123s                                                                               
    getAllMove: 22.8241s
    Il y à un cout, comme prévu (les insertions supplémentaires), mais ce n'est pas énorme non plus.

    edit : VS2012
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    getAll: 13.2198s
    getAllMove: 22.3273s
    Là par contre, ça commence à faire une sacrée différence

  18. #18
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tu arrivais avec ton ton péremptoire en disant que, de toutes manières, la sémantique de mouvement c'était pas pour les chiens, et laissant presque penser que c'est une opération " zero cost "
    Allons je ne voulais pas te brusquer, c'est juste que j'étais étonné que le sujet ne soit pas développé dans les posts, j'ai eu l'impression que vous étiez un peu passé à côté, ça peut arriver à tout le monde n'est-ce pas ?

    Je regrette si mon ton a paru agressif, ce n'était pas souhaité.
    Find me on github

  19. #19
    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
    Il y a aussi un coût au passage par référence : Comme le compilateur ne peut pas savoir s'il y a aliasing ou pas, il est obligé de supposer qu'il y en a, et de faire du code non optimal.

    De mon point de vue, et à moins d'en savoir plus sur la manière d'utiliser la fonction (et de se rendre compte que le pattern d'utilisation va à l'encontre de l'interface), je partirais en C++11 vers le plus simple : Une fonction qui retourne un vector.

    Elle apporte par rapport à une fonction prenant un paramètre en référence :
    - Une sémantique claire et non ambiguë.
    - La possibilité d'initialiser directement des variables à la bonne valeur, plutôt que de déclarer une variable à un moment et l'initialiser plus tard, ce qui augmente le nombre d'états d'un programme et complexifie la réflexion à son sujet
    - La possibilité de chaîner les appels de fonction processPhotoList(getPhotoList)).

    Dans certains contextes (référence sur les objets contenus, beaucoup d'appels pour récupérer les mêmes données), je retournerais un objet range désignant des données stockées ailleurs plutôt qu'un vector.

    En tout cas, je crois que dans le cas rare d'une fonction prenant un conteneur par référence pour le modifier, je n’appellerais jamais getXXX. appendXXX peut-être...
    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.

  20. #20
    Membre éclairé
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Points : 719
    Points
    719
    Par défaut
    Wow .... je m'attendais pas à autant de réponses. Merci beaucoup à tous/toutes.
    J'ai lu tous vos posts. J'ai rien vu qui m'ait "surpris", bien que j'aimerais relire les arguments avancés sur les performances à tête reposée (ça vaut la peine, Iradrille a même pris le temps de prendre des mesures )

    Toutefois, un point avancé par Koala me parait curieux :
    Citation Envoyé par koala01 Voir le message
    Cela pourrait prendre une forme sans doute très proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<typename It> void GetPhotoList (const std::string& device_name, It dest){
        FileHolder holder(device_name); //l'abstraction dont je te parlais la tantot
        std::copy(holder.begin(), holder.end(), dest);
    }
    En fait, si un tel objet FileHolder qui retourne une paire d'itérateurs était à ma disposition, je ne prendrais pas le temps d'implémenter une fonction GetPhotoList(), qui limite forcément les possibilités. Je laisserais l'appelant se débrouiller avec l'objet FileHolder directement, ça lui permettrait de copier la collection vers le conteneur de son choix, ou même d'effectuer certains traitements directement depuis le FileHolder.

    J'aime beaucoup les arguments avancés par JolyLoic sur l'idée de retourner directement un vecteur par valeur de retour, à la :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    std::vector<std::string> GetPhotoList(const std::string& device_name);
    Et effectivement, le nom "Get" de la fonction est un peu contradictoire avec l'idée de prendre un vecteur en référence en vue de le modifier.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 3 123 DernièreDernière

Discussions similaires

  1. Apprendre avec de bonnes habitudes : Ada ou Pascal ?
    Par steiner81 dans le forum Débuter
    Réponses: 15
    Dernier message: 03/06/2007, 00h38
  2. Fonction polymorphe et conteneur stl.
    Par Captain Fizzou dans le forum SL & STL
    Réponses: 2
    Dernier message: 29/11/2004, 19h13
  3. question générale sur les conteneurs
    Par tut dans le forum C++
    Réponses: 6
    Dernier message: 01/09/2004, 10h11
  4. Conteneurs associatifs à clés dupliquées
    Par Christophe Brun dans le forum Collection et Stream
    Réponses: 2
    Dernier message: 04/07/2004, 14h16

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