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

  1. #1
    Membre habitué
    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
    Points : 166
    Points
    166
    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 éclairé
    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
    Points : 879
    Points
    879
    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 é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 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 éclairé
    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
    Points : 879
    Points
    879
    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 habitué
    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
    Points : 166
    Points
    166
    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 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    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 éclairé
    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
    Points : 879
    Points
    879
    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 habitué
    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
    Points : 166
    Points
    166
    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
    Membre éclairé
    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
    Points : 879
    Points
    879
    Par défaut
    Au passage, boost propose un filter_iterator qui peut faire aussi bien / mieux que ton findMatch.
    (Au passage bis, pourquoi utiliser QVector alors qu'il y a un std::vector ?)

    Sinon, il me semble que le trouverFrere n'est pas la responsabilité du fichier, mais celle du parent, qui doit donner la liste des enfants, et de l'iterator, qui devrait les faire s'accéder dans l'ordre.
    Dès lors qu'on n'a plus cette méthode controversée, le problème ne se pose même plus !

    En bref, l'utilisation de ce genre de structure, selon moi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    bool HasTodoInHierarchy(Node const & n) { // recherche bidon
      for (Node::const_iterator it = n.begin(), e = n.end() ; it != e ; ++it) {
        if (it->IsFile() && it->Name() == "TODO") return true;
        if (it->IsDir() && HasTodoInHierarchy(*it)) return true;
      }
      return false;
    }
    A quoi servirait le trouverFrere ?

  10. #10
    Membre habitué
    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
    Points : 166
    Points
    166
    Par défaut
    Je ne connaissais pas filter_iterator je vais jeter un oeil. Mon extrait de code est simple, mais en fait il y a plusieurs passes, une série de Filter est appliqué à la suite pour affiner la recherche à chaque coup. Mais je vais voir ça.

    Pour l'utilisation de QVector il y a 2 raisons:
    - J'utilise Qt donc logiquement j'utilise ses conteneurs partout pour éviter les conversions, et rester cohérent.
    - Le copy on write des conteneurs Qt, qui d'ailleurs est utile ici puisque que findMatch() renvoie une copie de QVector ce qui serait extrêmement lourd avec un std::vector (bien qu'ici ce ne soit que des iterator)

    Sinon je crois que on commence à mélanger des choses, c'est surement de ma faute avec tous mes exemples.

    trouverFrere été un nom choisit au hasard il n'y a pas de lien avec mon programme dans lequel je chercherai le frère d'un fichier. J'aurai du choisir un autre nom. Dans ce cas tu aurais eu raison, chercher un fichier à partir d'un autre fichier n'a pas de sens.

    Plus clairement:

    FilesTree -> un conteneur de fichier
    FilesTree::findMatch(Filter) -> renvoie une liste d'itérateur selon un filtre donné

    D'ailleurs pour mieux comprendre, FilesTree::findMatch(Filter) est équivalent à une boucle de:
    std::find_if(FilesTree.begin(),FilesTree.end(),Filter);

  11. #11
    Membre éclairé
    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
    Points : 879
    Points
    879
    Par défaut
    Citation Envoyé par Nykoo Voir le message
    Je ne connaissais pas filter_iterator je vais jeter un oeil. Mon extrait de code est simple, mais en fait il y a plusieurs passes, une série de Filter est appliqué à la suite pour affiner la recherche à chaque coup. Mais je vais voir ça.
    Par contre, il me semble que filter_iterator filtre suivant une classe définie à la compilation. Si tu as plusieurs filtres à appeller, il suffit alors de les combiner dans ton filter principal avec, 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
     
    struct Filter {
      virtual bool operator() (const_iterator it) = 0;
    };
    struct MainFilter {
      std::vector<std::unique_ptr<Filter> > filters; // Ou std::auto_ptr
      AddFilter(std::unique_ptr<Filter> f) {
        filters.push_back(std::move(f));
      }
      bool operator () (const_iterator iter) {
        for (std::vector<std::unique_ptr<Filter> >::iterator it = filters.begin(), e = filters.end() ; it != e ; ++it) {
          if (!(*it)(iter)) return false;
        }
        return true;
      }
    };
    Pour l'utilisation de QVector il y a 2 raisons:
    - J'utilise Qt donc logiquement j'utilise ses conteneurs partout pour éviter les conversions, et rester cohérent.
    - Le copy on write des conteneurs Qt, qui d'ailleurs est utile ici puisque que findMatch() renvoie une copie de QVector ce qui serait extrêmement lourd avec un std::vector (bien qu'ici ce ne soit que des iterator)
    Le COW est aussi implémenté par la SL de G++, je crois. (d'ailleurs, il est bien facile pour un compilateur d'éviter même l'appel au constructeur de copie, dans ce cas - au moins si le vector est construit dans trouverFrere).

    [...]
    Plus clairement:

    FilesTree -> un conteneur de fichier
    FilesTree::findMatch(Filter) -> renvoie une liste d'itérateur selon un filtre donné

    D'ailleurs pour mieux comprendre, FilesTree::findMatch(Filter) est équivalent à une boucle de:
    std::find_if(FilesTree.begin(),FilesTree.end(),Filter);
    Et, alors, pourquoi y aurait-il besoin d'une troisième version en plus des deux habituelles ?

  12. #12
    Membre habitué
    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
    Points : 166
    Points
    166
    Par défaut
    Je vois, la méthode boost et la combinaison de filtre semble être une très bonne méthode. Le problème est que mon FilesTree n'est pas un simple conteneur linréaire, c'est un arbre. Interieurement l'algo de tri ne parcourt pas linéairement tous les éléments, ça serait peut efficace. Chaque filtre prend en compte sa position dans la hiérarchie et l'algo évite ou non de vérifier certaines branches en fonction du résultat.

    Le 2ème problème c'est que tout ça est dynamique et tous les filtres ne sont pas appliqués en même temps.
    A la demande de l'utilisateur j'appelle FindMatch pour un passe de Filtre, je garde le résulat (QVector) en mémoire. Quelques milisecondes/heures après un autre appel à findMatch peut être appliqué sur ce même QVector avec un filtre différent pour réaffiner encore la recherche (la taille de QVector diminue).

    C'est quoi que tu appelles la 3ème version? Une 3ème version à ces méthodes?

    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);
    };
    Je rappelle mon problème: je n'utilise jamais la méthode constante dans mon programme, car le résultat serait inexploitable. Pour ce problème de conception la méthode boost ne le règlerait pas d'ailleurs je ne pourais pas utiliser de const_iterator non plus.

  13. #13
    Membre habitué
    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
    Points : 166
    Points
    166
    Par défaut
    Citation Envoyé par Ekleog Voir le message
    Le COW est aussi implémenté par la SL de G++, je crois. (d'ailleurs, il est bien facile pour un compilateur d'éviter même l'appel au constructeur de copie, dans ce cas - au moins si le vector est construit dans trouverFrere).
    Au final la optimisations des compilateurs me donnent l'impression que ça ne sert à rien de bien coder. Quelqu'un peut écrire un truc horrible tel que:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct Moo {
    int var[10000];
    };
     
    Moo fonction(Moo m);
    Alors qu'on essaye quelque chose de plus logique:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct Moo {
    int var[10000];
    };
     
    const Moo& fonction(const Moo& m);
    Au final on perd du temps pour rien, c'est comme écrire ++i au lieu de i++, ou les for ([..] it != end[...]) au lieu de for ([..] it != vector.end()[...]), le compilateur nous vole la vedette en quelques sortes.

  14. #14
    Membre éclairé
    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
    Points : 879
    Points
    879
    Par défaut
    (Pour l'histoire des optimisations : )
    Sauf que retourner une référence sur un objet sur la pile qu'on vient de construire, c'est dur ...
    N'empêche, pour le retour par valeur, le compilateur n'est pas obligé de trouver le moyen d'optimiser, hein. Je pense surtout au cas de deux translation units séparées, où sans LTO ça devient difficile.

    (Pour en revenir au sujet : )
    Je vois, la méthode boost et la combinaison de filtre semble être une très bonne méthode. Le problème est que mon FilesTree n'est pas un simple conteneur linréaire, c'est un arbre. Interieurement l'algo de tri ne parcourt pas linéairement tous les éléments, ça serait peut efficace. Chaque filtre prend en compte sa position dans la hiérarchie et l'algo évite ou non de vérifier certaines branches en fonction du résultat.

    Le 2ème problème c'est que tout ça est dynamique et tous les filtres ne sont pas appliqués en même temps.
    A la demande de l'utilisateur j'appelle FindMatch pour un passe de Filtre, je garde le résulat (QVector) en mémoire. Quelques milisecondes/heures après un autre appel à findMatch peut être appliqué sur ce même QVector avec un filtre différent pour réaffiner encore la recherche (la taille de QVector diminue).
    Dans ce cas, la méthode filter_iterator est impossible ; ou du moins plus complexe que la solution maison.

    Par contre, la remarque du std::vector tient toujours : quitte à faire une classe, autant faire qu'elle soit la plus générique possible !

    D'ailleurs, il y a boost.filesystem qui pourrait totalement éliminer le besoin de Qt exprimé par cette classe ; ce qui permettrait de la réutiliser dans d'autres projets n'utilisant pas Qt.

    Je rappelle mon problème: je n'utilise jamais la méthode constante dans mon programme, car le résultat serait inexploitable. Pour ce problème de conception la méthode boost ne le règlerait pas d'ailleurs je ne pourais pas utiliser de const_iterator non plus.
    Même si tu ne l'utilises jamais, il vaut mieux qu'elle soit disponible.
    Et quel est alors le problème de conception ?

    Le problème réside peut-être dans la compréhension du const.
    Celui-ci doit être placé partout (enfin, le plus possible), sans utiliser de const_cast (enfin, le moins possible).
    Ensuite, il n'y a plus qu'à profiter de la sécurité apportée par le const : si une fonction prend un const, tu es sûr qu'elle ne modifie pas ; si elle prend une référence, tu es sûr qu'elle modifie - ou tout du moins qu'elle modifie potentiellement.

    En fait, où est le problème ?

  15. #15
    Membre habitué
    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
    Points : 166
    Points
    166
    Par défaut
    Mon problème est toujours celui du 1er post:

    J'ai conteneur, 2 méthodes, une constante et une non constante. Je n'utilise jamais la fonction non constante. Je me dis donc que je m'y prends peut être mal, que j'ai un problème de conception. Ou alors c'est tout à fait normal? Est que fournir 2 méthodes (une constante, une non constante) est la pratique classique? Apparement d'après la SL et Qt oui.

    J'ai juste un doute, je voudrais être sur.

    Pour boost::filesystem je ne l'utilise pas parce que je lis des données directement dans la MFT, et dans l'USN Journal avec des appel natifs à l'API WIN32. Qt fournit les outils nécessaires pour les opérations classiques du système de fichier autrement.

  16. #16
    Membre éclairé
    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
    Points : 879
    Points
    879
    Par défaut
    Citation Envoyé par Nykoo Voir le message
    Mon problème est toujours celui du 1er post:

    J'ai conteneur, 2 méthodes, une constante et une non constante. Je n'utilise jamais la fonction non constante. Je me dis donc que je m'y prends peut être mal, que j'ai un problème de conception. Ou alors c'est tout à fait normal? Est que fournir 2 méthodes (une constante, une non constante) est la pratique classique? Apparement d'après la SL et Qt oui.

    J'ai juste un doute, je voudrais être sur.

    [...]
    Oui, c'est la pratique classique. Et si jamais tu avais besoin d'avoir une version const ?

  17. #17
    Membre habitué
    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
    Points : 166
    Points
    166
    Par défaut
    Je suis donc rassuré.

  18. #18
    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
    Points : 13 017
    Points
    13 017
    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

  19. #19
    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,

    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

  20. #20
    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 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