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 :

const_iterator et contamination


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Avatar de Nykoo
    Profil pro
    Inscrit en
    Février 2007
    Messages
    234
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 234
    Par défaut const_iterator et contamination
    Bonjour,

    Je vais faire simple par l'exemple. Voici la fonction find de la stl:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<class InputIterator, class T>
      InputIterator find ( InputIterator first, InputIterator last, const T& value )
      {
        for ( ;first!=last; first++) if ( *first==value ) break;
        return first;
      }
    find est une fonction qui consulte, elle ne modifie rien. Il est donc logique d'utiliser des const_iterator avec:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::vector<int> v(4,0);
    std::vector<int>::const_iterator begin = v.begin();
    std::vector<int>::const_iterator end = v.end();
    std::vector<int>::const_iterator it = std::find(v.begin(),v.end(),0);
    Après cette opération en "lecture seule", je ne pourrai jamais utiliser le résultat (mon iterator it) pour modifier v. Vous me direz d'utiliser des iterator à la place des const_iterator dans la fonction find. Très bien, mais il y a 2 problèmes:
    - Il n'y a aucun sens à utiliser des iterators non constants dans une fonction qui ne modifie rien.
    - Au final on n'utilise plus le const_iterator nul part.

    L'exemple est volontairement très simple. Mais prenons l'exemple d'un conteneur fait maison il est indispensable de fournir 2 versions pour chaque méthode consultative:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct Famille {
     
        struct iterator { [...]
        };
        struct const_iterator { [...]
        };
     
        const_iterator trouverFrere(const_iterator membre) const;
        iterator trouverFrere(iterator membre);
     
    };
    La méthode trouverFrere(iterator membre) préserve l'état de la structure mais n'est pas constante. Bien sur l'état pourra être changé par l'intermédiaire de l'iterator retourné, mais la fonction elle même ne modifie rien. Elle devrait donc être const.

    Je pose cette question parce que tous les conteneurs de mon programme sont voué à être modifié à un moment ou à un autre. Donc à aucun moment je ne peut utiliser de fonctions constantes sous peine de ne plus pouvoir modifier le conteneur. Ca rend le concept de const beau mais inutilisable.

    Un problème de conception? Peut être, mais alors quelles sont les solutions?

  2. #2
    Membre émérite
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Par défaut
    La SL faisant la duplication de fonctions membres, je pense que c'est une bonne idée de faire pareil.
    Et, comme en général les fonctions renvoyant des iterator sont assez peu nombreuses, il n'est pas gênant de les doubler en ayant l'une qui fait appel à l'autre. (au passage, implémenter la version non const à partir de la const est la bonne méthode, selon Effective C++).

  3. #3
    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 Nykoo Voir le message
    - Il n'y a aucun sens à utiliser des iterators non constants dans une fonction qui ne modifie rien.
    Une fonction qui ne modifie rien se caractérise par un const en fin de signature, pas par la constance de ce qu'elle retourne.
    Mais cela s'entend pour une fonction membre, par rapport aux données membre.

  4. #4
    Membre émérite
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Par défaut
    Sauf si l'iterator pointe vers la structure elle-même, auquel cas retourner un iterator serait une violation du contrat (au même titre que le serait un T & operator [] (size_t) const dans vector)

  5. #5
    Membre confirmé
    Avatar de Nykoo
    Profil pro
    Inscrit en
    Février 2007
    Messages
    234
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 234
    Par défaut
    Citation Envoyé par Ekleog Voir le message
    La SL faisant la duplication de fonctions membres, je pense que c'est une bonne idée de faire pareil.
    Et, comme en général les fonctions renvoyant des iterator sont assez peu nombreuses, il n'est pas gênant de les doubler en ayant l'une qui fait appel à l'autre. (au passage, implémenter la version non const à partir de la const est la bonne méthode, selon Effective C++).
    C'est ce que j'ai fait, d'ailleurs c'est le seul endroit où j'ai utilisé un const_cast appliqué sur le retour de la méthode const utilisé dans la méthode non constante.

    Citation Envoyé par oodini Voir le message
    Une fonction qui ne modifie rien se caractérise par un const en fin de signature, pas par la constance de ce qu'elle retourne.
    Mais cela s'entend pour une fonction membre, par rapport aux données membre.
    Je parlais plutôt des arguments de type constant même si il se trouve que le retour est constant également. Mais justement ta remarque et d'après: http://www.parashift.com/c++-faq-lit...html#faq-18.10 qui ne parle que de l'état de l'objet qui doit être conservé (pas les arguments) cela me fait penser qu'il est possible d'avoir une 3 méthode (une constante) avec des arguments non constant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    iterator trouverFrere(iterator membre) const;
    En plus des 2 autres:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    const_iterator trouverFrere(const_iterator membre) const;
        iterator trouverFrere(iterator membre);
    Mais je n'ai jamais vu ça, car on retourne un iterator non constant qui comme Ekleog dit:

    Citation Envoyé par Ekleog Voir le message
    Sauf si l'iterator pointe vers la structure elle-même, auquel cas retourner un iterator serait une violation du contrat (au même titre que le serait un T & operator [] (size_t) const dans vector)

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 152
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 152
    Billets dans le blog
    4
    Par défaut
    const_iterator trouverFrere(const_iterator membre) const;
    iterator trouverFrere(iterator membre);
    et pourquoi ne pas écrire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    iterator trouverFrere(const_iterator membre) const;
    ?
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  7. #7
    Membre émérite
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Par défaut
    Ah. Etrange.
    La constness du résultat dépend de la constness de la Famille, mais aussi de celle de l'argument passé ?
    Que fait en réalité ta méthode trouverFrere ?

  8. #8
    Membre confirmé
    Avatar de Nykoo
    Profil pro
    Inscrit en
    Février 2007
    Messages
    234
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 234
    Par défaut
    Citation Envoyé par Bousk Voir le message
    et pourquoi ne pas écrire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    iterator trouverFrere(const_iterator membre) const;
    ?
    Parce que iterator permettra de modifier le contenu, et il existe déjà une méthode ayant la même signature:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    const_iterator trouverFrere(const_iterator membre) const;
    Ma méthode trouverFrère est en réalité une méthode de mon conteneur FilesTree qui contient des éléments portant chacun des infos sur un fichier.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class FilesTree {
    [...]
    QVector<const_iterator> findMatch(const Filter<const_iterator>& filter) const;
    QVector<iterator>       findMatch(const Filter<iterator>& filter);
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<class T>
    struct Filter {
    [...]
    bool operator()(const T&) const;
    };
    findMatch utilise Filter sur chacun des éléments de FilesTree pour mettre ou non l'itérateur de cet élément dans la liste retournée (QVector<const_iterator> ou QVector<iterator>).

    J'utilise ensuite cette liste pour consulter les éléments de FilesTree. Des fois juste pour lire les infos, des fois pour les modifier. Donc au final je n'utilise jamais les const_iterator. Chaque iterateur me permet d'afficher un fichier dans une vue en liste. La plupart du temps l'utilisateur consulte juste les fichiers, mais parfois il les supprime, renomme... d'où la contrainte qu'ils soit modifiables.

  9. #9
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Salut,
    Je crois qu'à la base, le quiproquo se situe par là (c'est moi qui souligne) :
    Citation Envoyé par Nykoo Voir le message
    find est une fonction qui consulte, elle ne modifie rien. Il est donc logique d'utiliser des const_iterator avec
    La mutabilité/constance de l'itérateur (dans le cadre du concept d'itérateur dans son utilisation dans les algorithmes génériques) est orthogonale à la mutabilité/constance de l'objet désigné par l'itérateur.

    Un itérateur mutable (non constant) est un itérateur qui satisfait à la catégorie d'itérateur 'output'. Un itérateur est constant s'il est non mutable par rapport à cette dernière définition (ce seront principalement les itérateurs de catégorie 'input').

    Par là, on voit que les algorithmes sont agnostiques quant à la mutabilité ou la constance de l'élément proposé par l'itérateur. Les algorithmes ne modifiant pas la séquence vont demander des itérateurs constants au sens ci dessus, c'est à dire principalement des itérateurs de catégorie Input (*). Les algorithmes modifiant la séquence vont quant à eux demander un itérateur de catégorie 'output'.
    Ensuite, l'élément désigné pourra aussi bien être constant que mutable.

    Par exemple, voici un bout de code qui passe un itérateur dont l'élément est forcément mutable à find et qui est parfaitement valide :
    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
    #include <algorithm>
    #include <string>
    #include <iterator>
    #include <sstream>
     
    int main()
    {
     
       std::string str("un deux trois quatre cinq six");
     
       std::istringstream iss(str);
       std::istream_iterator<std::string> itss(iss);
       auto it = std::find(itss,std::istream_iterator<std::string>(),"deux");
     
       return 0;
    }
    Si la signature de find avait imposé que l'élément désigné par l'itérateur soit constant, alors il m'aurait été impossible d'écrire les lignes précédentes ... car il n'existe tout simplement pas d'itérateur de flux ne modifiant pas celui-ci.

    A contrario, voici ci-dessous un itérateur dont l'élément désigné est constant sur l'élément pointé mais satisfaisant la catégorie 'output' demandée par l'algorithme fill :
    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
    #include <algorithm>
    #include <vector>
     
     
    struct dummy
    {
       int operator=(int rhs) const
       {
          return ++rhs;
       }
    };
     
    int main()
    {
       std::vector<dummy> const v(10);
       std::vector<dummy>::const_iterator begin = v.begin();
       std::vector<dummy>::const_iterator end = v.end();
     
       std::fill(begin,end,10);
       return 0;
    }
    oui, oui, l'exemple est débile car en pratique les itérateurs purement 'output' seront non constant.
    En revanche, les itérateurs de catégorie forward, bidirectionnal ou access qui sont par conséquent 'output' peuvent très bien nécessiter d'être constant sur l'élément désigné.


    (*) certains de ces algorithmes ne modifiant pas la séquence prennent des itérateur de catégorie 'forward' non pas pour le côté 'output' mais parce que la catégorie 'forward' est la première qui ne soit pas de simple passe.

    La conclusion est donc ensuite biaisée :
    Citation Envoyé par Nykoo Voir le message
    - Il n'y a aucun sens à utiliser des iterators non constants dans une fonction qui ne modifie rien.
    cf ci-dessus
    Citation Envoyé par Nykoo Voir le message
    - Au final on n'utilise plus le const_iterator nul part.
    C'est que la constance est probablement mal respectée dans les autres fonctions et variables utilisées. Sinon ton principal soucis serait d'avoir à tout rendre constant

  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
    Salut,

    Pour abonder dans le sens de 3D:

    De prime abord, il faut être attentif à ce que l'on souhaite rendre constant :

    • Une fonction membre qui ne modifie pas l'objet concerné devra etre déclarée constante
    • Si un paramètre de fonction n'a pas vocation à être modifié dans la fonction, il faut le déclarer constant
    • Si une référence (sur un membre de la classe correspondant au type de l'objet au départ duquel on invoque la fonction) renvoyée par un fonction membre n'a pas vocation à être modifiée, il faut déclarer la fonction et la référence renvoyée constante
    • Si une fonction renvoie une référence sur un de ses paramètres, tu peux augmenter la contrainte en rajoutant la constance, mais pas (ou du moins rarement) etre plus laxiste
    • Si tu as besoin des deux versions d'une fonction, n'hésite pas à créer les deux

    NOTA: si tu dois avoir deux versions d'une même fonction, l'idéal est de baser la version non constante sur l'appel de la version constante.

    De cette manière, tu t'éviteras bien des soucis, si tu dois modifier le comportement de la fonction, du au fait que tu aurais oublié de modifier l'une des versions

    Cela prendrait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Type const & MaClass::operator[](size_t index) const
    {
        assert(index < size);
        return member_[index];
    }
    Type & MaClass::operator[](size_t index) const
    {
        return const_cast<Type &>(static_cast<MaClass const &>(*this)[index]);
    }
    Pour terminer: veilles en permanence à déléguer correctement tes responsabilités:

    Si, pour une raison ou une autre, tu en viens à devoir utiliser des const_iterator et des iterator (sur une même collection, s'entend) au sein d'une seule et même fonction, et, surtout si tu en viens à envisager de changer le type d'un const_iterator, c'est très certainement parce que la fonction en fait trop
    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

    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 Nykoo
    J'ai conteneur, 2 méthodes, une constante et une non constante. Je n'utilise jamais la fonction non constante.
    C'est extrêmement surprenant. Cela veut dire que, par exemple, tu ne passes jamais ton conteneur par référence constante à une fonction ??

    Par exemple, std:vector::begin() et std::vector::end() sont toutes les deux fournies avec deux surcharges, une constante renvoyant des const_iterator, l'autre non-constante renvoyant des iterator. Du coup on a :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void byConstRef(const std::vector<int>& v)
    {
        // std::vector<int>::iterator = v.begin(); // ne compile pas
         std::vector<int>::const_iterator = v.begin(); // ok
    }
     
    void byRef(std::vector<int>& v)
    {
         std::vector<int>::iterator = v.begin(); // ok
         std::vector<int>::const_iterator = v.begin(); // ok, on peut rajouter une contrainte de constance si l'on en a envie.
    }
    Citation Envoyé par Nykoo
    Après cette opération en "lecture seule", je ne pourrai jamais utiliser le résultat (mon iterator it) pour modifier v.
    Encore heureux !!
    Si v.begin() et v.end() renvoi un const_iterator cela veut dire que l'on est dans un cas où l'objet est constant !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void byConstRef(const std::vector<int>& v))
    {
       std::vector<int>::const_iterator begin = v.begin();
       std::vector<int>::const_iterator end = v.end();
       std::vector<int>::const_iterator it = std::find(v.begin(),v.end(),0);
       *it = 5; // heureusement, ne compile pas
    }
    Du fait que v soit passé par const ref on est forcé de manipuler uniquement des const_iterator ce qui évite de se tirer dans le pied.

    Par contre si le std::vector<int> est modifiable alors il suffit d'utiliser directement des iterator :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void byRef(std::vector<int>& v))
    {
       std::vector<int>::iterator begin = v.begin();
       std::vector<int>::iterator end = v.end();
       std::vector<int>::iterator it = std::find(v.begin(),v.end(),0);
       *it = 5; // ok dans ce cas
    }
    Si std::find avait la signature que tu proposes, iterator find(const_iterator, const_iterator, value_type) alors ça voudrait dire que l'on pourrait par inattention faire de gros dégâts du style :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void foo(const std::vector<int>& v)
    {
       std::vector<int>::const_iterator begin = v.begin(); 
       std::vector<int>::const_iterator end = v.end();
       std::vector<int>::iterator it = std::find(v.begin(),v.end(),0);
       *it = 5; // arg v est passé par réf const et il a quand même était modifié
    }

Discussions similaires

  1. soucis const_iterator/iterator sans raison apparente
    Par DEVfan dans le forum SL & STL
    Réponses: 6
    Dernier message: 12/12/2008, 22h53
  2. map<>::const_iterator iter et iter++
    Par neismarspra dans le forum C++
    Réponses: 7
    Dernier message: 18/09/2008, 12h34
  3. [Prototype] FF : contamination input par balise englobante
    Par Zwiter dans le forum Bibliothèques & Frameworks
    Réponses: 2
    Dernier message: 16/05/2008, 17h28
  4. map et const_iterator
    Par Faiche dans le forum SL & STL
    Réponses: 2
    Dernier message: 06/12/2007, 09h26

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