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 :

problème avec un foncteur abstrait


Sujet :

C++

  1. #1
    Membre émérite
    Homme Profil pro
    Inscrit en
    Décembre 2007
    Messages
    758
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Décembre 2007
    Messages : 758
    Par défaut problème avec un foncteur abstrait
    bonjour à tous,

    Je rencontre un problème avec un foncteur abstrait. Je souhaite avoir une classe conteneur munie d'une méthode 'sort' qui prend un foncteur en argument qui sera ensuite transmis à std::sort de <algorithm>.

    J'ai pour cela défini une interface pour mon foncteur:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <typename T>
    class ISorter
    {
        public:
        virtual bool operator()(T a, T b) = 0;
    };
    Mon conteneur se présente comme ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    class List
    {
        public:
        //constructeur, destructeur et autres méthodes omis...
        void sort(ISorter<T> s);
    };
    Ensuite je déclare une implémentation de ISorter:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class IntSorter: public ISorter<int>
    {
        public:
        //constructeur et destructeur omis
        bool operator()(int a, int b);
    };
    dans mon programme ensuite:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    List<int> lst;
    //j'alimente ma liste en entiers
    lst.sort(IntSorter());
    et mon compilo (gcc 4.4.5 sur ubuntu) me crie dessus (comme souvent )

    il me retourne une erreur sur la dernière ligne que j'ai écrite ici:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    error: cannot allocate an object of abstract type ‘ISorter<int>’
    Ok, je ne peux pas déclarer une instance d'un type abstrait. Mais ici je déclare une instance d'un type concret. J'imagine qu'il y a un truc qui ch.. dans la colle au niveau de la déclaration de:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void sort(ISorter<T> s);
    Je me suis dit que ça venait peut être de la recopie de l'objet (je n'ai pas défini d'opérateur de copie, mais comme il me semble qu'il ne peut pas être polymorphique je suis embêté), mais j'ai testé une version qui prend un ISorter<T>* sans plus de résultat.

    J'imagine que quelqu'un a une explication pour un noob comme moi

    Merci

  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 : 50
    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
    Par défaut
    Déjà, je ne sais pas trop ce que tu veux obtenir au juste, mais ça me semble un peu étrange, j'ai l'impression que tu te compliques la vie pour pas grand'chose.

    Sinon, à part ça, tu as la fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
        void sort(ISorter<T> s);
    Qui nécessite une instance de ISorter<int>, et donc problème. Tu dis que tu as essayé de passer un ISorter<T>* sans plus de résultat. Normalement, ce serait à même de résoudre ce problème (mieux, passer un ISorter<T>& ou un ISorter<T> const &). Peux-tu détaille ce que tu as fait ?
    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
    Homme Profil pro
    Inscrit en
    Décembre 2007
    Messages
    758
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Décembre 2007
    Messages : 758
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Déjà, je ne sais pas trop ce que tu veux obtenir au juste, mais ça me semble un peu étrange, j'ai l'impression que tu te compliques la vie pour pas grand'chose.
    d'un côté je veux un conteneur générique disposant d'une méthode de tri qui utilise l'algo std::sort. d'un autre côté je veux pouvoir utiliser différentes méthodes de tri en fonction des objets qui seront contenus. je veux décolérer au maximum le conteneur de la méthode de tri pour les faire évoluer indépendamment.

    maintenant, je suis débutant en C++ (pas en POO), donc s'il y a une approche qui est communément admise comme meilleure, je suis curieux de la connaitre. J'ai lu ci et là qu'il vaut mieux privilégier les foncteurs sur les pointeurs de fonction pour tout ce qui est callback.


    Citation Envoyé par JolyLoic Voir le message
    Peux-tu détaille ce que tu as fait ?
    en utilisant un passage par pointeur, le problème se déporte sur l'appel de std::sort qui n'accepte pas un pointeur vers un foncteur. Je suis donc obligé de passer par une indirection et c'est à ce moment là que ça coince (je me retrouve avec le message d'erreur que j'ai signalé ci-dessus).

  4. #4
    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
    Ici : void sort(ISorter<T> s); le passage se fait par valeur, le compilateur cherche donc à instancier un ISorter<int> vers lequel il copierait l'instance de IntSorter et ça ne se passe mais pas du tout bien. En C++, pour manipuler un objet à partir de son type de base, il faut passer par une référence ou un pointeur.

    Mais il est vrai que cela ne fait que décaler ton problème car la STL manipule les foncteurs par valeurs. Et donc, ça va râler au moment de l'appel de l'algorithme.

    Il faut donc utiliser std::cref qui est une enveloppe pour passer un foncteur par référence constante.

    Ce qui pourrait donner :
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    template <typename T>
    class ISorter
    {
        public:
        virtual bool operator()(T a, T b) const = 0;
    };
     
    class IntSorter: public ISorter<int>
    {
        public:
        //constructeur et destructeur omis
        bool operator()(int a, int b) const
        {
            return a<b;
        }
    };
     
    #include <vector>
    #include <algorithm>
    #include <functional>
    template <typename T>
    class List
    {
        public:
        List(){}
        //constructeur, destructeur et autres méthodes omis...
        void sort(ISorter<T> const &s)
        {
            std::sort(my_list.begin(),my_list.end(),std::cref(s));
        }
    private:
        std::vector<T> my_list;
    };
     
    int main()
    {
     
        List<int> lst;
        //j'alimente ma liste en entiers
        lst.sort(IntSorter());
        return 0;
    }
    Cependant si ce code fonctionne sur MinGW, Visual C++ râle vu son implémentation de std::(c)ref.

    Une première solution est donc de passer par l'idiom pimpl (enveloppe/lettre) en mettant le foncteur abstrait dans la lettre et en passant l'enveloppe à la liste :
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    #include <memory>
     
    template <typename T>
    class ISorterImpl
    {
        public:
        virtual bool operator()(T a, T b) const = 0;
    };
     
     
    template<class T>
    class Sorter
    {
    public:
     
        Sorter(ISorterImpl<T> *p):sorter(p)
        {}
     
        bool operator()(T a, T b) const
        {
            return (*sorter)(a,b);
        }
    private:
        std::shared_ptr<ISorterImpl<T> > sorter;
    };
     
     
    class IntSorterImpl: public ISorterImpl<int>
    {
        public:
        //constructeur et destructeur omis
        bool operator()(int a, int b) const
        {
            return a<b;
        }
    };
     
    #include <vector>
    #include <algorithm>
    #include <functional>
     
    template <typename T>
    class List
    {
        public:
        List(){}
        //constructeur, destructeur et autres méthodes omis...
        void sort(Sorter<T> s)
        {
            std::sort(my_list.begin(),my_list.end(),s);
        }
    private:
        std::vector<T> my_list;
    };
     
    int main()
    {
     
        List<int> lst;
        //j'alimente ma liste en entiers
        lst.sort(Sorter<int>(new IntSorterImpl));
        return 0;
    }
    Mais, pourquoi ne pas carrément faire de sort une fonction générique prenant le prédicat en paramètre sans en forcer une quelconque abstraction ?
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #include <memory>
     
    class IntSorter
    {
        public:
        //constructeur et destructeur omis
        bool operator()(int a, int b) const
        {
            return a>b;
        }
    };
     
    #include <vector>
    #include <algorithm>
    #include <functional>
     
    template <typename T>
    class List
    {
        public:
        List(){}
     
        template<class Sorter_t_>
        void sort(Sorter_t_ s)
        {
            std::sort(my_list.begin(),my_list.end(),s);
        }
    private:
        std::vector<T> my_list;
    };
     
    int main()
    {
     
        List<int> lst;
        //j'alimente ma liste en entiers
        lst.sort(IntSorter());
        lst.sort(std::less<int>());
        lst.sort(std::greater<int>());
        return 0;
    }
    L'approche générique est, au vu de ce que tu montres, ce qui me semble le plus appropriée.

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2010
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2010
    Messages : 118
    Par défaut
    Tu peux obtenir une bonne décorrélation entre ton foncteur et ton conteneur de manière statique en paramétrant ta classe List à l'aide d'un autre paramètre template qui servira à définir ton foncteur, cependant ton approche par classe abstraite est à bannir, tes foncteurs devront être des classes instanciables.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T , typename Functor>
    class List
    {
        public:
        //constructeur, destructeur et autres méthodes omis...
        void sort(Functor f);
    };
    Une autre approche que je qualifierais de dynamique serait de pouvoir créer des foncteurs dont le code est modifiable à l'exécution (en excluant le polymorphisme de type vu que std :: sort prend un objet en paramètre), ainsi tu pourrais encapsuler un pointeur de fonction dans ton foncteur, ainsi tu pourrais modifier à tout moment ce pointeur pour modifier dynamiquement le code exécuté par ton foncteur.

    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
    template <typename T>
    struct ISorter
    {
        bool (*m_pf)(T a , T b );
     
        ISorter ( bool (*pf)(T a , T b ) ) : m_pf ( pf ){}
        virtual bool operator()( T a, T b ){ return m_pf ( a , b ); }
    };
     
    template <typename T>
    class List
    {
        public:
        //constructeur, destructeur et autres méthodes omis...
        void sort(ISorter<T>& s){}
    };
     
    bool func1 ( int a , int b ){}
    bool func2 ( int a , int b ){}
    //etc...
     
    int main ()
    {
        List<int> lst;
        ISorter<int> f ( func1 );
    //j'alimente ma liste en entiers
        lst.sort(f);
        return 0;
    }
    D'ailleurs la classe ISorter devient quasiment inutile puisque la STL définit déja de tel foncteur générique (pointer_to_binary_function), ainsi que des fonctions de conversions de pointeur de fonction vers foncteur(ptr_fun).

    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
     
    #include <functional>
     
    template <typename T>
    class List
    {
        public:
        //constructeur, destructeur et autres méthodes omis...
        void sort( std :: pointer_to_binary_function<T,T,bool> f ){}
    };
     
    bool func1 ( int a , int b ){}
    bool func2 ( int a , int b ){}
    //etc...
     
    int main ()
    {
        List<int> lst;
    //j'alimente ma liste en entiers
        lst.sort( std :: ptr_fun (func1) );
        return 0;
    }

  6. #6
    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 : 50
    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
    Par défaut
    Le plus simple est de laisser tomber tout ce qui est lié à ton ISorter<T> :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    template <typename T>
    class List
    {
        public:
        //constructeur, destructeur et autres méthodes omis...
        template<class Comparer>
        void sort(Comparer s);
    };
     
    template <class T>
    template <class Comparer>
    void List<T> sort(Comparer s)
    {
      std::sort(bebin(), end(), s);
    }
    Si vraiment tu as besoin du polymorphisme, rien ne t’empêche alors d'utiliser un comparateur polymorphe, mais ça n'impactera en rien le design de ta classe Liste.
    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.

  7. #7
    Membre émérite
    Homme Profil pro
    Inscrit en
    Décembre 2007
    Messages
    758
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Décembre 2007
    Messages : 758
    Par défaut
    merci à tous

    me voilà avec du grain moudre

    j'aime beaucoup la solution consistant à changer la fonction membre sort en template.

    même si je n'utilise pas VC++, j'ai peur que la solution std::(c)ref soit problématique du fait de la gestion différente par les compilos et me semble moins "propre".

    j'ai besoin de tester les autres solutions proposées, notamment celles de backlash.

    un grand merci encore

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

Discussions similaires

  1. Problème avec classe abstraite
    Par Antoniom dans le forum Débuter avec Java
    Réponses: 4
    Dernier message: 29/01/2011, 13h03
  2. problème avec les classes abstraite avec C#
    Par takfa2008 dans le forum C#
    Réponses: 1
    Dernier message: 29/06/2009, 10h25
  3. Problème d'héritage avec une classe abstraite
    Par Ph.denis dans le forum C++
    Réponses: 7
    Dernier message: 22/03/2008, 10h37
  4. [std::list][find_if] problème avec mes foncteurs
    Par n!co dans le forum SL & STL
    Réponses: 12
    Dernier message: 04/02/2005, 11h56
  5. Problème avec la mémoire virtuelle
    Par Anonymous dans le forum CORBA
    Réponses: 13
    Dernier message: 16/04/2002, 16h10

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