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

SL & STL C++ Discussion :

std::iterator, Vector.begin() et généricité


Sujet :

SL & STL C++

  1. #1
    Membre éprouvé

    Profil pro
    Inscrit en
    Octobre 2003
    Messages
    1 163
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 163
    Points : 1 148
    Points
    1 148
    Par défaut std::iterator, Vector.begin() et généricité
    Bonjours à tous,

    voici quatre ans que je développe exclusivement C#. J'ai aujourd'hui un projet à réaliser en C++, le retour n'est pas aussi facile que cela.
    Voici mon problème:

    J'ai défini une classe dont une méthode doit me retourner "un tableau de chaines de caractères". Voulant conserver une certaine généricité je souhaite retourner un iterateur de chaine de caractères afin de m'affranchir du type de conteneur. Je n'ai ici besoin que de pouvoir énumérer dans une boucle type foreach l'ensemble des chaines.

    J'ai donc le prototype suivante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual std::iterator<std::input_iterator_tag, std::string> MaMethode()
    que je chercher à implémenter:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    std::iterator<std::input_iterator_tag, std::string> MaMethode()
    {
       std::vector<std::string> *result = new std:vector<std::string>(); 
       result->push_back(std::string("Test1"));
       result->push_back(std::string("Moi"));
       return result->begin();
    }
    1. Cela ne compile pas. J'ai un message m'indiquant que ce que retourne std::vector::begin() ne peut être casté en std::iterator<std::input_iterator_tag, std::string>. Soit. Comment dans ce cas définir ma méthode pour tirer partie de la généricité des itérateurs et pouvoir retourner celui sur mon tableau?
    2. Se pose ensuite la question du nettoyage: il n'y a pas de garbage collector en C++. Quel design adopter pour s'assurer que mon std::vector result sera détruit ou puisse etre détruit par l'appelant de ma méthode MaMethode?


    Merci de m'aider à clarifier tout cela dans ma petite tête,

    Neilos
    Neilos

  2. #2
    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
    Effectivement, on voit que tu as fait du C# récemment. En particulier, en C#, les conteneurs sont placés dans une hiérarchie d'héritage, et il est de bon ton de retourner une classe de base de cette hiérarchie pour rendre le code plus robuste au changement.

    Ce n'est pas le cas en C++ (ni pour les conteneurs, ni pour les itérateurs), la philosophie est légèrement différente : Ce n'est pas parce que tu retourne un vector que ton code n'est pas générique. Il suffit que l'utilisateur se sache pas qu'il s'agit d'un vecteur, et tu pourras alors le modifier. Le plus simple est de retourner un typedef.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    typedef vector<string> MaDonnee;
    MaDonnee MaMethode();
    Tant que l'utilisateur n'utilise pas des spécificités de vector, il te sera possible de remplacer vector par string. Et s'il en utilise, c'est qu'il en avait besoin
    Retourner un type concret n'empêche pas du tout l'utilisation d'algorithmes génériques sur ce type en C++. Un tel algorithme sera implémenté à l'aide de templates, qui ne nécessitent pas d'héritage. Dans le code suivant, find marche quel que soit le type MaDonnee, tant que ce type fourni des iterateurs sur string à partir de begin et end.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    MaDonnee d = MaMethode();
    MaDonnee::iterator it = find(d.begin(), d.end(), "Test");
    Maintenant, si tu veux vraiment restreindre les fonctions que l'utilisateur va pouvoir appeler sur la sortie, tu peux retourner des itérateurs, mais toujours pareil ,tu fais un typedef sur vector<string>::const_iterator, et c'est ce typedef que tu exposes et demande à tes utilisateurs d'utiliser.

    Par contre :
    - Il te faudra alors fournir deux méthode, begin et end, sinon l'utilisateur aura du mal à arrêter sa boucle
    - Comme tu le vois, il y a un problème pour libérer le vecteur. Cette méthode a plus sa place dans un environnement où le vecteur est une donnée membre de ta classe, et que tu veux permettre d'y accéder. Si tu veux retourner un conteneur nouvellement créé, tu retourne un conteneur, pas des itérateurs dessus.

    Donc, dans ton cas où tu retourne un nouveau tableau, le plus simple serait, si tu veux vraiment restreindre l'utilisateur, de retourner une instance d'une classe à toi, qui wrap un vecteur en n'en fournissant qu'une interface réduite. Mais définir cette interface réduite et implémenter une telle classe a souvent un coût difficilement justifiable.

    Si tu n'utilises que des compilateurs modernes, une autre solution s'offre à toi (elle existe depuis longtemps dans le principe, mais la syntaxe pour l’utiliser la rend difficilement recommandable). Si tout ce que tu veux, c'est que l'utilisateur puisse itérer, ne lui laisse pas le choix, itère à sa place, en lui demandant juste quoi faire à chaque itération :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template<class F>
    void MaMethode(F f)
    {
       std::vector<std::string> result;
       result.push_back(std::string("Test1"));
       result.push_back(std::string("Moi"));
       for_each(result.begin(), result.end(), f);
    }
    que l'utilisateur appellera :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    c.MaMethode([](string const &s)
        {
             cout << s << endl;
        });
    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.

  3. #3
    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
    Bonjour,

    Pour éviter masse de bug en C++ après une exposition prolongé au C# je dirais qu'il n'y a qu'un seul conseil vraiment important à retenir : Supprime 90% de tes new !!
    Dès que ton instinct te souffle d'écrire Truc* t = new T(); alors en C++ il sera souvent meilleur d'écrire Truc t;


    Autre chose, je conseillerais de laisser tomber la généricité pour le moment. Pars sur des algos avec des types concrets dans un premier temps et si vraiment tu notes au fur et à mesure beaucoup de duplication de code avec des algos quasi identique sur des types différents alors cela vaudra le coup de se poser la question à ce moment.

    Citation Envoyé par Neilos
    J'ai un message m'indiquant que ce que retourne std::vector::begin() ne peut être casté en std::iterator<std::input_iterator_tag, std::string>
    Oups, pas de chance. En fait std::iterator ne doit quasiment jamais être utilisé dans du code utilisateur. C'est uniquement une petite classe (Pas très bien nommé faut l'admettre) dont on dérive pour aider à l'implémentation d'un nouveau type d'itérateur.

    Citation Envoyé par Neilos
    Je n'ai ici besoin que de pouvoir énumérer dans une boucle type foreach l'ensemble des chaines.
    Malheureusement, les boucles foreach (qu'ont appelle range for-loop en C++) n'ont été standardisé que depuis très peu de temps et ne sont disponible que sur les compilateurs très récents. Il reste les bonnes vieilles boucles for en attendant.

    Pour éviter des problèmes de durée de vie, perso dans un premier temps je retournerai simplement le vector<string> par valeur. Par exemple :
    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
    std::vector<std::string> MaMethode()
    {
       std::vector<std::string> result; 
       result.push_back("Test1");
       result.push_back("Moi");
       return result;
    }
     
    void foo()
    {
      std::vector<std::string> v = MaMethode();
       for(std::vector<std::string>::iterator it = v.begin(), end = v.end(); it != end; ++it)
      {
         std::cout << *it << std::endl;
      }
    } // <- v est détruite ici
     
    int main()
    {
      foo();
    }
    Et si tu as la chance d'avoir un compilo très récent :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
       for(const std::string& s : MaMethode())
       {
          std::cout << s << std::endl;
       } 
       // <- le vector des string est détruit en sortant de la boucle for
    }
    Après le code évoluant et se complexifiant il est probable que ce vector<std::string> se retrouvera de toute façon membre d'un objet et donc sa durée de vie sera la même que celle de l'objet en question.

    Par exemple :
    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
     
    struct MaStruct
    {
       typedef std::vector<std::string>::iterator Iterator;
     
       void Add(const std::string& s)
      {
         vec.push_back(s);
       }
     
       Iterator begin() { return vec.begin(); }
       Iterator end() { return vec.end(); }
     
       std::vector<std::string> vec;
    };
     
    void foo()
    {
       MaStruct ms;
       ms.Add("Test1");
       ms.Add("Moi");
     
       for(MaStruct::Iterator it = ms.begin(), end = ms.end() ; it != end ; ++it)
      {
          std::cout << *it << std::endl;
       }
    } // <- ms est détruit ici, en sorti de la fonction, le vector de string contenu à l'intérieur est détruit aussi
     
    int main()
    {
      foo();
    }
    Là encore si tu as la chance d’avoir un compilo très récent....
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    int main()
    {
        MaStruct ms;
        ms.Add("Test1");
        ms.Add("Moi");
     
       for(const std::string& s : ms)
      {
         std::cout << s << std::endl;
      }
    }

  4. #4
    Membre éprouvé

    Profil pro
    Inscrit en
    Octobre 2003
    Messages
    1 163
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 163
    Points : 1 148
    Points
    1 148
    Par défaut
    Merci à vous deux pour votre réponses.
    Effectivement le C# et les interfaces etc ont la vie dure dans ma tête...

    Donc si je comprend bien il n'existe pas d'équivalent à IEnumerable<T> en C++.
    Est-ce que cela serait pertinent et/ou facile de définir une telle classe abstraite pour mon usage propre?

    Dans tous les cas dans un premier temps je vais effectivement me contenter d'utiliser std::vector via un typedef. Cela simplifie aussi la gestion de la mémoire.

    Concernant le fait de ne pas utiliser de new je ne suis pas complètement d'accord avec toi. Mes lointains souvenirs me disent qu'en particulier si cet objet est instancié dans ma méthode et que je souhaite le retourner il vaudra mieux faire un new.

    Je marque ce post comme résolu, même si la discussion peux continuer en particulier à propos de cet équivalent d'IEnumerable<T>.
    Neilos

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

Discussions similaires

  1. Probléme iterator vector
    Par rxjmo dans le forum Langage
    Réponses: 9
    Dernier message: 19/02/2013, 16h11
  2. Réponses: 5
    Dernier message: 22/12/2010, 09h46
  3. Problème création d'iterator ( Vector et Map )
    Par LittleWhite dans le forum SL & STL
    Réponses: 11
    Dernier message: 27/03/2009, 22h00
  4. indice d'un std::iterator !
    Par mr_samurai dans le forum SL & STL
    Réponses: 3
    Dernier message: 28/10/2008, 19h02
  5. Réponses: 10
    Dernier message: 30/06/2008, 19h59

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