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

Langage C++ Discussion :

template, "interface", implementation


Sujet :

Langage C++

  1. #1
    Membre du Club
    Inscrit en
    Avril 2007
    Messages
    143
    Détails du profil
    Informations personnelles :
    Âge : 37

    Informations forums :
    Inscription : Avril 2007
    Messages : 143
    Points : 57
    Points
    57
    Par défaut template, "interface", implementation
    Bonjourno ,

    Voila je débute le C++ et je me heurte a des petites difficultés.

    J'essaye de créer une "interface"(je mets des "" car d'apres ce que j'ai compris en C++ ce n'est pas la même interface qu'en java, notion de concept en C++, non?J'aurais aussi besoin de quelques explications sur ce point ) qui sera commune a plusieurs de mes futures classes, mais je ne sais pas comment implementer tout ca.

    Je vous montre mon code pour une meilleure compréhension

    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
    /* Interface.hh */
    template<typename T>
    class interface {
    public:
    	static T zero();
    	virtual T &operator*(const T &);
    };
    
    /* implementation.hh */
    #include "Interface.hh"
    
    class implementation : public interface<implementation> {
    public:
    	implementation();
    	// autre méthode
    };
    
    /* implementation.cc */
    #include "implementation.hh"
    
    implementation::implementation() {}
    
    // Comment redéfinir les méthodes de mon template?
    Est ce la meme facon pour une méthode statique ou non
    Lorsqu'on surcharge un opérateur doit on surcharger les deux méthodes(celle qui accepte un pointeur et celle qui accepte une référence)?

    Merci pour votre aide, ou pour des liens qui pourrait m'expliquer ces mécanismes

  2. #2
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Bonjour,

    En C++, une interface est simplement une classe dont toutes les méthodes sont virtuelles et pures, c'est à dire qu'elles ne sont pas implémentées.

    On ne peut instancier une interface, on doit d'abord la faire hériter par une classe qui implémente ses méthodes virtuelles pures.

    Voici la manière d'effectuer:

    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
     
    class MonInterface
    {
       public:
          virtual void methode1() = 0;
          virtual std::string methode2( std::string& str ) = 0;
    };
     
    class MaClasse : public MonInterface
    {
       public:
          void methode1();
          std::string methode2( std::string& );
    };
     
    void MaClasse::methode1()
    {
       //...
    }
     
    std::string MaClasse::methode2( std::string& str )
    {
       //...
       return( str );
    }
     
    MaClasse monObjet;
    Le but d'une interface est de pouvoir avoir des listes ou des tableaux d'objets différents héritants tous d'une même interface.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
        std::list< MonInterface* > maListe;
        MaClasse *a = new MaClasse;
        maListe.push_back(a);
    Tu peux ensuite effectuer les méthodes déclarées par l'interface sans effectuer de cast.
    Exemple

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
       for( std::list< MonInterface* >::iterator it = maListe.begin(); it != maListe.end(); ++it )
       {
          (*it)->methode1();
       }
    Et lorsque sur surcharge un opérateur, il faut surcharger toutes les possibilités pour lesquels tu peux effectuer l'action indiquée, du moins celles dont tu as besoin.

  3. #3
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    Pour completer ce qu'a très justement dit Julien,
    j'ajouterais pour ceux qui ont déjà fait du java le petit comparatif suivant :

    en C++ : une classe peut hériter de plusieurs classes à la fois.
    Ca peut poser des problèmes quasi inextricables :
    Si D hérite de B et C et que B et C héritent de A, on a un héritage en diamant.
    L'instance de A se retrouve 2 fois dans D et c'est l'enfer...
    il faut introduire la notion d'héritage virtuel et c'est pas de la tarte !

    en Java : on ne peut hériter que d'une seule classe à la fois

    en Java : on peut hériter plusieurs interfaces à la fois. Mais une interface n'implemente rien et surtout n'a pas de membre.

    en C++ : Pour régler les problèmes d'héritage en diamant il suffit de déclarer une classe qui n'a pas de membre. En théorie ça suffit.
    Mais pour reprendre le très élégant principe des interfaces à la Java on s'impose en plus de n'avoir que des méthodes virtuelles pures (dont la déclaration se termine par "= 0").

    PS : "en diamant" vient de la forme en losange du diagramme de classe de A, B, C et D.


    Hadrien
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  4. #4
    Membre du Club
    Inscrit en
    Avril 2007
    Messages
    143
    Détails du profil
    Informations personnelles :
    Âge : 37

    Informations forums :
    Inscription : Avril 2007
    Messages : 143
    Points : 57
    Points
    57
    Par défaut
    Merci pour votre aide !
    Cependant je pensais utilisé un template car mes classes qui vont en hériter n'auront pas le même type de retour pour leur fonction... Donc si j'utilise une classe toute simple comment spécifier le type de retour de mes méthode?

    Si on reprend mon code de départ, j'avais l'intention de faire en sorte que ma classe qui va implementer la méthode zero() de "interface" puisse retourner le type de sa propre classe donc en l'occurrence ici se serait implementation et de la meme facon pour mes autres classes qui implémenteraient "interface". D'ou mon utilisation d'un template... Est ce une mauvaise utilisation??

    Je me trompe peut etre totalement j'espere avoir reussi a expliquer un peu mieux ce que je souhaite obtenir comme résultat...

    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
    /* Interface.hh */
    template<typename T>
    class interface {
    public:
    	virtual T zero() =0;
    	virtual T &operator*(const T &)=0;
    };
    
    /* implementation.hh */
    #include "Interface.hh"
    
    class implementation : public interface<implementation> {
    public:
    	implementation();
    	// autre méthode
    };
    Merci pour votre aide !!!

  5. #5
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    ooops !
    pour répondre à ta question violette :

    quand on surcharge un operateur, il n'y a que la version avec une référence à implementer. On peut implementer à la place une version avec passage de paramètre par valeur mais c'est déconseillé.

    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
    class Nombre
        {
        public:
            Nombre ( int x = 0 )
                {
                _n = x ;
                }
     
            Nombre operator+ ( const Nombre & x ) // (1) très bien !
                {
                return( _n + x._n ) ;
                }
     
            Nombre operator+ ( Nombre x ) // (2) on peut aussi mais exclusif avec (1)
                {                         // et surtout, perte de perf et de mémoire
                return( _n + x._n ) ;     // si Nombre est gros
                }
     
            Nombre operator+ ( const Nombre * x ) // (3) inutile !
                {
                return( _n + x->_n ) ;
                }
     
        private:
            int _n ;
        } ;
     
     
    void main ()
        {
        Nombre n1 = 1 ;
        Nombre n2 = 2 ;
        Nombre n3 ;
        n3 = n1 + n2 ;  // ce qu'on voulait mais ne compile pas à cause de l'ambiguité entre (1) et (2)
        n3 = n1 + &n2 ; // code bizarre et illisible rendu possible par (3) -> à éviter !!!
        }
    Moralité : (1) c'est bien (2) et (3) ça craint !

    Hadrien
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  6. #6
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Citation Envoyé par line86 Voir le message
    Si on reprend mon code de départ, j'avais l'intention de faire en sorte que ma classe qui va implementer la méthode zero() de "interface" puisse retourner le type de sa propre classe donc en l'occurrence ici se serait implementation et de la meme facon pour mes autres classes qui implémenteraient "interface". D'ou mon utilisation d'un template... Est ce une mauvaise utilisation??

    Je me trompe peut etre totalement j'espere avoir reussi a expliquer un peu mieux ce que je souhaite obtenir comme résultat...
    Merci pour votre aide !!!
    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
     
    #include <iostream>
    #include <string>
    #include <list>
     
    template< typename T >
    class MonInterface
    {
    public:
        //...
        virtual T& methode3() = 0;
    };
     
    class MaClasse : public MonInterface<MaClasse>
    {
        //...
    public:
     
        // implementation de l'interface
        MaClasse& methode3();
     
        // autre methodes
     
     
    };
     
    //...
     
    MaClasse& MaClasse::methode3()
    {
        //...
        return *this; // ou quelque chose d'autre, ici c'est juste à titre d'exemple
    }
     
    class AutreClasse : public MonInterface<AutreClasse>
    {
        //...
    public:
     
        // implementation de l'interface
        AutreClasse& methode3();
     
        // autre methodes
    };
     
    AutreClasse& AutreClasse::methode3()
    {
        //...
        return *this; // ou quelque chose d'autre, ici c'est juste à titre d'exemple
    }
     
    int main()
    {
        MaClasse *mc = new MaClasse;
        AutreClasse *ac = new AutreClasse;
        MonInterface< MaClasse >* mc_i_p = mc;
        MonInterface< AutreClasse >* ac_i_p = ac;
    }
    Ce code est parfaitement valide.

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Dans un contexte d'héritage, il te manque sans doute la notion de retour covariant...

    Cette notion te permet, si une fonction virtuelle renvoie une référence ou un pointeur sur un objet dont le type est celui d'une classe de base de redéfinir, pour une classe dérivée la fonction en lui faisant renvoyer une référence ou un pointeur sur un objet de type dérivé.

    Tu peux donc en arriver à quelque chose comme
    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    #include <iostream>
    using namespace std;
    class Base
    {
        public:
            virtual Base& Fonction1(/*...*/);
            virtual Base& Fonction2(/*...*/);
            virtual Base& Fonction3(/*...*/);
    };
    class Derivee1 : public Base
    {
        public:
            virtual Derivee1& Fonction1(/*...*/);
            virtual Derivee1& Fonction2(/*...*/);
            virtual Derivee1& Fonction3(/*...*/);
    };
    class Derivee2 : public Base
    {
        public:
            virtual Derivee2& Fonction1(/*...*/);
            virtual Derivee2& Fonction2(/*...*/);
            virtual Derivee2& Fonction3(/*...*/);
    };
    Base& Base::Fonction1()
    {
        cout<<"Base::fonction1"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Base
                       */
    }
    Base& Base::Fonction2()
    {
        cout<< "Base::fonction 2"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Base
                       */
    }
    Base& Base::Fonction3()
    {
        cout<<"Base::fonction3"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Base
                       */
    }
    Derivee1& Derivee1::Fonction1()
    {
        /* aurait pu faire appel explicitement à la méthode adaptée à un type 
         * Base:
        Base::Fonction1();
         */
        cout<<"Derivee1::fonction1"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Derivee1
                       */
    }
    Derivee1& Derivee1::Fonction2()
    {
        /* aurait pu faire appel explicitement à la méthode adaptée à un type 
         * Base:
        Base::Fonction2();
         */
        cout<< "Derivee1::fonction 2"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Derivee1
                       */
    }
    Derivee1& Derivee1::Fonction3()
    {
        /* aurait pu faire appel explicitement à la méthode adaptée à un type 
         * Base:
        Base::Fonction3();
         */
        cout<<"Derivee1::fonction3"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Derivee1
                       */
    }
    Derivee2& Derivee2::Fonction1()
    {
        /* aurait pu faire appel explicitement à la méthode adaptée à un type 
         * Base:
        Base::Fonction1();
         */
        cout<<"Derivee2::fonction1"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Derivee2
                       */
    }
    Derivee2& Derivee2::Fonction2()
    {
        /* aurait pu faire appel explicitement à la méthode adaptée à un type 
         * Base:
        Base::Fonction2();
         */
        cout<< "Derivee2::fonction 2"<<endl;
        return *this; /* renvoie une référence sur l'objet courent, qui est
                       * du type Derivee2
                       */
    }
    Derivee2& Derivee2::Fonction3()
    {
        /* aurait pu faire appel explicitement à la méthode adaptée à un type 
         * Base:
        Base::Fonction3();
         */
        cout<<"Derivee2::fonction3"<<endl;
        return *this;
    }
    /**********************************************/
    int main()
    {
        cout<<"Creation et utilisation d'un objet de type Derive1"<<endl;
        Derivee1 d1;
        d1.Fonction1()
          .Fonction2()
          .Fonction3();
        cout<<"faisons passer cet objet comme etant du type de base "
            <<"et recommencons"<<endl;
        Base& b = d1;
        b.Fonction1()
         .Fonction2()
         .Fonction3();
    }
    L'avantage que tu aura ici est que tu obtiendra un comportement parfaitement et réellement polymorphe: Le type réel de l'instance manipulée est testé au moment de l'exécution.

    En effet, il faut bien prendre conscience que le concept de programmation générique (à base de template) implique le fait que les types sont déterminés au moment de la compilation...

    Ainsi, si tu as une classe template proche de
    template <class T>
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class MyTemplate
    {
        /*...*/
    };
    que tu fais dériver respectivement en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Derivee1 : public MyTemplate<Derivee1>
    {
    };
    et en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Derivee2 : public MyTemplate<Derivee2>
    {
    };
    tes classes Derivee1 et Derivee2 ne seront pas officiellement considérées comme... ayant une base identique, ce qui implique que tu ne pourra pas atteindre un objectif polymorphe dans le sens de faire passer "allègrement" un objet du type dérivé comme étant du type de base (dans le respect du principe de substitution)...

    La classe de base pour Derivee1 sera MyTemplate<Derivee1>, alors que la classe de base pour Derivee2 sera... MyTemplate<Derivee2>...
    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

  8. #8
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    J'essaye de créer une "interface"(je mets des "" car d'apres ce que j'ai compris en C++ ce n'est pas la même interface qu'en java, notion de concept en C++, non?J'aurais aussi besoin de quelques explications sur ce point ) qui sera commune a plusieurs de mes futures classes, mais je ne sais pas comment implementer tout ca.

    [code qui fait du CRTP, mais avec des fonctions virtuelles...]
    Clairement, tu mélanges tout.
    Avant d'essayer de faire du CRTP, commence déjà par comprendre les bases. Faire du CRTP avec des fonctions virtuelles n'a pas vraiment de sens.

    Les concepts, ce n'est pas ça du tout. Ce sont des méta-types, et ça ne marche qu'à la compilation.

    Ce que tu veux faire toi, c'est juste de l'héritage, comme en Java. Toute fonction membre est virtuelle dans ce langage.
    Boost ftw

  9. #9
    Membre du Club
    Inscrit en
    Avril 2007
    Messages
    143
    Détails du profil
    Informations personnelles :
    Âge : 37

    Informations forums :
    Inscription : Avril 2007
    Messages : 143
    Points : 57
    Points
    57
    Par défaut
    Merci beaucoup pour toutes vos réponses

    Je vais tenter de mettre tout ca en application

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