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 :

std::unordered_set et std::hash


Sujet :

C++

  1. #1
    Membre actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    538
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 538
    Points : 262
    Points
    262
    Par défaut std::unordered_set et std::hash
    Bonjour,

    je souhaite créer une classe 'Ensemble' qui hérite de 'std::unordered_set' :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<class T>
    class Ensemble : public std::unordered_set<T> {
        ...
    }
    J'ai plusieurs classe qui hérite de la classe 'Element' :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class A : public Element { ... }
    class B : public Element { ... }
    J'ai défini la fonction de Hashage pour 'Element' :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    namespace std {
        template <>
        struct hash<Element> {
            size_t operator()(const Element & e) const noexcept {
                return e.getId(); // Unique Id
            }
        };
    }
    Le problème c'est que je ne parviens pas à faire des ensemble de A ou B :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int main() {
        Ensemble<Element> mySet_1; // Ok  
        Ensemble<A> mySet_2; // Pas Ok
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    error: no match for call to '(const std::hash<A>) (const A&)'|
    Quelqu'un peut-il m'expliquer le fonctionnement de std::hash ? Ou est l'erreur ? Merci.

  2. #2
    Expert éminent sénior

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

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 474
    Points
    11 474
    Billets dans le blog
    11
    Par défaut
    Salut!

    Pour commencer, les conteneurs de la STL ne sont pas fait pour faire partie d'un héritage (tu verras qu'il n'y a aucune fonction membre virtuelle dans ces classes).
    Je te conseille donc de mettre ton std::unordered_set en tant que membre de ta classe Ensemble, quitte à forwarder les fonctions dont tu as besoin (begin, end, find, insert...).

    Ensuite, pour ton problème, tu as défini un hash pour Element, mais pas pour A.
    Tu remarqueras qu'il te demande bien un std::hash< A >, cela ne tient pas compte de l'héritage que tu as mis en place.
    Tu peux le définir comme ceci, en utilisant ton std::hash< Element >:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    namespace std {
        template <>
        struct hash<A> {
            size_t operator()(const A & a) const noexcept {
                return hash<Element>()( a );
            }
        };
    }
    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).

  3. #3
    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 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Salut,

    bah l'erreur, le compilateur te l'indique, error: no match for call to '(const std::hash<A>) (const A&)'| que ne comprends-tu pas ?
    Il n'existe aucune fonction std::hash<A>.. et pourquoi faire un héritage de std::unordered_set ? C'est peu commun et peu recommandé.
    De même que ta façon d'utiliser un std::unordered_set. Il existe un paramètre template pour définir sa fonction de hash, pas besoin de spécialiser le paramètre par défaut fourni par la std.
    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.

  4. #4
    Membre actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    538
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 538
    Points : 262
    Points
    262
    Par défaut
    Citation Envoyé par dragonjoker59 Voir le message
    Ensuite, pour ton problème, tu as défini un hash pour Element, mais pas pour A.
    Tu remarqueras qu'il te demande bien un std::hash< A >, cela ne tient pas compte de l'héritage que tu as mis en place.
    [/code]
    Oui j'ai bien compris. Mon but est de ne pas définir hash pour chaque classe : A, B, ... C'est pour cela que j'ai créé la classe Element. Comment faire ?

    En quoi l'héritage pose problème. J'ai besoin d'un objet std::unordered_set avec des méthodes supplémentaires ... je n'ai pas le choix, non ?

  5. #5
    Membre actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    538
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 538
    Points : 262
    Points
    262
    Par défaut
    Est-ce correct comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct HashElement {
        size_t operator() (const Element & e) const {
           return e.getId();
        }
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<class T>
    class Ensemble : public std::unordered_set<T, HashElement> {
    ...
    }

  6. #6
    Expert éminent sénior

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

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 474
    Points
    11 474
    Billets dans le blog
    11
    Par défaut
    A part pour l'héritage, oui.
    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).

  7. #7
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par dragonjoker59 Voir le message
    A part pour l'héritage, oui.
    Quel est le problème avec cet héritage, s'il ne fait pas de polymorphisme ?

  8. #8
    Expert éminent sénior

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

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 474
    Points
    11 474
    Billets dans le blog
    11
    Par défaut
    Ce laïus complet (bien qu'en anglais) devrait pouvoir répondre à ta question: http://stackoverflow.com/questions/6...110262#7110262
    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).

  9. #9
    Invité
    Invité(e)
    Par défaut
    Merci pour le lien. Je comprends les idées défendues mais j'accroche moyen au raccourci qui ressort du laius comme quoi "composition = bien, héritage = pas bien", sans considération du problème traité. En plus, le gars nous évoque une théorie censée le justifier mais il ne donne qu'un exemple en guise d'explication.

    Bref, pour en revenir à l'héritage en question, je pense qu'il peut être tout à fait valable s'il n'y a pas de polymorphisme ou de sous-héritage compliqués en jeu, et qu'il vaut mieux une solution simple qui répond juste au problème qu'une solution plus compliquée qu'on pourra réutiliser "peut-être éventuellement un jour".

  10. #10
    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 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Hériter d'une classe juste pour en avoir l'interface (set, vector, list,...)
    - oui on en abuse parce que ça simplifie et accélère les choses
    - ça reste un abus
    - on ne promeut pas une mauvaise pratique à un débutant
    L'héritage ça indique un concept fort, ce n'est pas un gadget parce qu'on veut un push_back sur notre classe.
    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.

  11. #11
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut
    Hériter d'une classe il ne faut le faire que lorsqu'il y a au moins une méthode virtuelle sinon on s'expose à certains problèmes:
    Si dans l'exemple suivant Foo hérite de std::vector<int> alors le code suivant est syntaxiquement correct:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::vector<int>* p = new Foo;
    delete p; //<-- ici on seul le destructeur de std::vector<int> sera appelé; si Foo fait de l'allocation dynamique on aura un memory leak
    La solution si on souhaite récupérer l'interface de std::vector à peu de frais c'est d'utiliser l'héritage privé + les using:
    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 <vector>
    #include <iostream>
     
    class Foo : private std::vector<int>
    {
        public:
        using std::vector<int>::size;
        using std::vector<int>::push_back;
    };
     
    int main() {
        Foo f;
        std::cout << f.size() << std::endl; //affiche 0
        f.push_back(3);
        std::cout << f.size() << std::endl; //affiche 1
    }

  12. #12
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par CedricMocquillon Voir le message
    Hériter d'une classe il ne faut le faire que lorsqu'il y a au moins une méthode virtuelle
    N'importe quoi. Dans ce cas, pourquoi le C++ ne met-il pas les méthodes virtuelles par défaut (comme en java, il me semble) ? Le polymophisme induit un coût et une complexité supplémentaires donc son utilisation doit se justifier.

    Et tant qu'on est dans le n'importe quoi, en quoi passer l'héritage en privé est-il une solution aux fuites mémoires dans ton exemple ?

  13. #13
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut
    Ho là, doucement, il ne faut pas s'emporter; je n'ai peut être pas été assez clair dans mes explications de mon précédent post, je m'en excuse. Voilà un exemple complet: cpp.sh/6wjph
    Pour ceux qui ont la flemme d'aller voir le lien, voici l'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
    #include <vector>
    #include <iostream>
     
    class Foo : public std::vector<int>
    {
    public:
        Foo() {
            std::cout << "constructor call" << std::endl;
            data_ = new int[10];
        }
     
        ~Foo() {
            std::cout << "destructor call" << std::endl;
            delete[] data_;
        }
     
    private:
        int* data_;
    };
     
    int main() {
        std::vector<int>* p = new Foo;
        delete p;
    }
    Avec un héritage public d'une classe de base sans méthode virtuelle (ce qui est le cas des classes conteneur de la STL), on ne voit affiché que "constructor call", ce qui implique donc une fuite mémoire puisque le désctructeur de Foo (celui qui libère data_) n'est pas appelé.
    Maintenant le même exemple avec un héritage privé:
    le code:
    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
    #include <vector>
    #include <iostream>
     
    class Foo : private std::vector<int>
    {
    public:
        Foo() {
            std::cout << "constructor call" << std::endl;
            data_ = new int[10];
        }
     
        ~Foo() {
            std::cout << "destructor call" << std::endl;
            delete[] data_;
        }
     
    private:
        int* data_;
    };
     
    int main() {
        std::vector<int>* p = new Foo;
        delete p;
    }
    ben ça compile tout simplement pas! On a "main.cpp:22:31: error: 'std::vector<int>' is an inaccessible base of 'Foo'
    std::vector<int>* p = new Foo;"

    Maintenant c'est syntaxiquement possible et avec beaucoup de rigueur c'est possible de faire de l'héritage publique sans méthode virtuelle mais pour moi c'est juste chercher un jour ou l'autre les ennuis (c'est comme faire appel au couple new / delete, c'est possible de le faire sans fuite mémoire mais aujourd'hui avec C++14 c'est reconnu qu'on peut développer sans et qu'on s'en porte mieux)

  14. #14
    Invité
    Invité(e)
    Par défaut
    Désolé si je me suis emporté mais j'en ai un peu marre d'être pris pour un newbie parce que j'ai un avis différent de certains (et pourtant partagé par d'autres).

  15. #15
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut
    Non non y'a pas de soucis, c'est l'inconvénient des forums: on ne sait pas quel est le ton employé par les intervenants. C'est vrai que cela fait un moment que je n'étais pas passé sur le forum et que je ne connais pas les nouveaux (ou moins nouveaux ^^), mais il y a bien une différence entre nouveau et newbie!
    Maintenant, bien que l'on ait un peu disgressé par rapport au post initial, je ne vois toujours pas dans quel cas c'est "utile" (je distingue "utile" de "ça permet de taper un moins de lignes de code" au détriment potentiellement de la sécurité) de faire de l'héritage public sans virtuelle dans la base; je m'explique:
    je vois plusieurs cas possibles:

    - on a une classe de base POD avec une dérivée elle même POD: on niveau mémoire l'héritage public est équivalent à un membre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct Base 
    { 
      int i; 
    };
     
    struct Derivee : public Base
    {
      double d;
    };
    est équivalent en mémoire à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct Base 
    { 
      int i; 
    };
     
    struct Derivee
    {
      Base b;
      double d;
    };
    La seule différence c'est que l'on ne peut pas passer une derivee là ou on attend une base mais syntaxiquement parlant on a juste à faire un ".b" pour résoudre ce problème. Sémantiquement parlant je trouve ça "sain" puisqu'on est dans le cas de POD (souvent associés à des types "valeur") donc à mon sens on n'a pas à mélanger les choux et les carottes

    - on a une classe de base non POD et on veut juste "récupérer" l'interface; dans ce cas, l'héritage privé avec exportation de l'interface "utile" via les using permet également de ne pas mélanger les choux et les carottes (intérêt de "ça ne compile pas" dans mon précédent post)

    - on veut mélanger les choux et les carottes (ou plutôt on une carotte étant un légume on veut pouvoir passer des carottes là où on attend des légumes) dans ce cas on est dans le cas d'un polymorphisme dynamique et on a bien interêt à utiliser une classe de base avec au minimum un destructeur virtuel sinon on peut avoir des memory leak (cf. mon précédent post). Note bien qu'on peut avoir des memory leak ou tout autre problème de libération de ressource géré par les classes RAII.


    Maintenant j'ai "forgé" mon avis à partir de nombreuses lectures sur ce forum (grace notamment à des intervenant d'extrème qualité comme Luc, Emmanuel, Loïc, Philippe et j'en oublie d'autre) et d'autres sources. C'est un avis qui peut évoluer mais il me faudrait un cas "concret" qui montre que "ça vaut le coup" sans engendrer de réels problèmes.

    Après c'est au contraire très bien que l'on ait des avis divergents c'est comme ça que l'on peut confronter les arguments pro / cons l'idée étant de ne pas "camper" sur nos positions mais de prendre en compte la rationnalité des arguments avancés.

  16. #16
    Invité
    Invité(e)
    Par défaut
    Ok pas de soucis; merci pour cette explication.

    Un exemple de cas où j'utiliserais plutôt un héritage public sans fonction virtuelle, c'est justement un cas comme celui présenté au début de la discussion : j'ai besoin d'un conteneur proche d'un des conteneurs de la STL mais avec quelques méthodes en plus ou modifiées; je vais utiliser ce nouveau conteneur directement (pas besoin de polymorphisme) et je n'ai pas d'allocation dynamique particulière à y ajouter. Ici, l'héritage public avec instanciation statique est simple, efficace et pas particulièrement dangeureuse niveau mémoire.

    Après oui, il y a plein d'autres cas où ce ne sera pas suffisant donc c'est bien de le mentionner. Mais de là à conseiller à un "débutant" de sortir automatiquement l'artillerie lourde au cas où on voudrait réutiliser sur tous les problèmes du monde, je pense que c'est un bon moyen de le convaincre de changer de langage.

  17. #17
    Expert éminent sénior

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

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 474
    Points
    11 474
    Billets dans le blog
    11
    Par défaut
    Tout dépend de ce qu'on considère comme l'artillerie lourde ^^'.
    Pour moi l'artillerie lourde, c'est l'héritage.
    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).

Discussions similaires

  1. la difference entre deux std::unordered_set
    Par mohsenuss91 dans le forum C++
    Réponses: 7
    Dernier message: 21/04/2015, 17h30
  2. Équivalent de std::min et std::max en C?
    Par vdumont dans le forum C
    Réponses: 2
    Dernier message: 08/10/2006, 18h15
  3. conversion std::string en std::istringstream
    Par flipper203 dans le forum SL & STL
    Réponses: 3
    Dernier message: 06/07/2006, 18h34
  4. std::cout et std::wstring
    Par glKabuto dans le forum SL & STL
    Réponses: 11
    Dernier message: 10/06/2006, 18h44
  5. std::sort() sur std::vector()
    Par tut dans le forum SL & STL
    Réponses: 20
    Dernier message: 05/01/2005, 19h15

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