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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    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
    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,

    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 confirmé

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

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 033
    Billets dans le blog
    12
    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
    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 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 éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    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
    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 );

  7. #7
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    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é
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 290
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 290
    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é.

  9. #9
    Membre chevronné
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    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.

  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 phi1981 Voir le message
    Toutefois, un point avancé par Koala me parait curieux :

    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.
    Ne t'attardes peut être pas trop sur la manière dont j'ai utilisé FileHolder, qui n'était qu'un exemple, d'ailleurs sans doute mal choisi au demeurant (surement mal choisi, à lire ta réaction )

    Ce qui importe, c'est surtout d'arriver à utiliser une abstraction capable de représenter "n'importe quel système de fichiers".

    Qu'il s'agisse d'un serveur, d'une tablette, d'un GSM, d'un GPS ou du troisième sous répertoire du disque E de ton pc, c'est le même combat et tu devrais pouvoir utiliser la même abstraction : tu as un dossier, composé sans doute de répertoires et de fichiers, dont tu peux récupérer le nom et d'autres informations (comme la date et l'heure de la création, la date et l'heure de la dernière modification, les droits pour différents types de personnes, ...) qui te permet de travailler "de la même manière, quelles que soient les conditions de travail".

    Je penses, par exemple, à quelque chose comme boost.filesystem qui présente ce genre d'abstraction et qui fonctionne avec n'importe quel système de fichier concret.

    La manière d'utiliser une telle abstraction dépend évidemment très largement de l'interface publique quelle présente, et c'est à toi d'en tenir compte
    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 chevronné
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Ne t'attardes peut être pas trop sur la manière dont j'ai utilisé FileHolder, qui n'était qu'un exemple, d'ailleurs sans doute mal choisi au demeurant (surement mal choisi, à lire ta réaction )

    Ce qui importe, c'est surtout d'arriver à utiliser une abstraction capable de représenter "n'importe quel système de fichiers".
    Oui ok j'aurais dû arriver à cette conclusion vu que la classe de l'objet est "FileHolder" et non pas un truc genre "PhotoExtractor". Désolé

    Bon j'ai collecté assez d'exemples pour réfléchir tranquillement à mes histoires de conteneurs. Merci beaucoup tout le monde !

  12. #12
    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 phi1981 Voir le message
    Oui ok j'aurais dû arriver à cette conclusion vu que la classe de l'objet est "FileHolder" et non pas un truc genre "PhotoExtractor". Désolé
    Allez, je propose donc un très diplomatique "tords partagés" sur ce coup là
    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

  13. #13
    Membre Expert
    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
    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.

+ Répondre à la discussion
Cette discussion est résolue.

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