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 :

Template, classe Bouton


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut Template, classe Bouton
    Bonjour,

    J'ai récemment appris à faire des template et je suis légèrement bloqué.

    J'ai une classe bouton avec un template :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    template<typename DONNEE1, typename DONNEE2, typename FONCTION1, typename FONCTION2>
    class Bouton
    DONNEE1 est le type de l'argument qui sera utilisée pour FONCTION1
    DONNEE2 est le type de l'argument qui sera utilisée pour FONCTION2

    Mais quand il n'y a pas d'argument, j'aimerais bien définir :
    Bouton<void, void, ma_fonction1, ma_fonction2>
    Ou quand il n'y pas une des deux fonction,
    Bouton<void, void, void, ma_fonction2>

    Mais quand je fait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    template <typename DONNEE2, typename FONCTION1, typename FONCTION2>
    void Bouton<int, DONNEE2, FONCTION1, FONCTION2>::setDonneeClique()
    {
        std::cerr << "Cette fonction ne prend pas d'argument" << std::endl;
    }
    il le compilateur me dit :
    C:\Users\Neckara\Desktop\Patcher (sources)\src\..\include\Bouton.h|107|error: invalid use of incomplete type 'class Bouton<int, DONNEE2, FONCTION1, FONCTION2>'|
    C:\Users\Neckara\Desktop\Patcher (sources)\src\..\include\Bouton.h|9|error: declaration of 'class Bouton<int, DONNEE2, FONCTION1, FONCTION2>'|
    ||=== Build finished: 2 errors, 0 warnings ===|
    NB : j'ai mis 'int' pour vérifié si ce n'est pas pas 'void' qui posait problème


    Le deuxième problème qui se pose, c'est que j'ai une classe Principale qui a deux sous-classes Bouton (jouer et desinstaller) ainsi qu'une sous-classe Serveur (gère les mises à jours).

    Mais il faut que Serveur ai un pointeur de type Bouton sur chacun des deux Bouton de Principal.
    Or la fonction a exécuter par le Bouton jouer est une méthode de Principal.
    J'ai donc :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Bouton <void *, void *,void (*)(void *), void (Principal::*)(void *)>Jouer;
    Dans Principal (les void * sont là parce que je n'ai pas encore réussi à mettre des void)

    Mais pour définir un pointeur Jouer il faut que je mettre dans Serveur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Bouton <void *, void *,void (*)(void *), void (Principal::*)(void *)>* Jouer;
    Mais Serveur ne connait pas Principal puisque Serveur est une sous-classe de Principal.
    Si je met l'en-tête de Principal au début de celle de Serveur, ceci ne marchera pas puisque l'en-tête de Principal aura besoin de celle de Serveur avant, c'est un peu le serpent qui se mort la queue.
    J'ai tenté de faire un :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    extern class Principal;
    Mais comme attendu ceci n'a pas marché...

    Auriez-vous des idées?

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 485
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 485
    Par défaut
    Hello,

    Le moins que l'on puisse dire, c'est que c'est TRÈS tordu. Tu te poses ces cas de figure pour l'exercice ou tu as réellement architecturé ton programme de la sorte ?

    Pour la question 1 : Quand tu ajoutes « <> » à la suite d'un identifiant, c'est pour définir un cas d'usage ou pour l'instancier. Pas pour le déclarer. Ce que tu veux faire, en fait, c'est utiliser un nom de type par défaut si celui-ci n'est pas explicitement spécifié. Dans ce cas, ça se fait dans la déclaration de « template <…> »., comme pour les fonctions :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template <typename DONNEE1=int>
    … par contre, il y a des chances pour que tu sois obligé de mettre ces paramètres par défaut à la fin de ta déclaration.

    Pour la question 2 : si tu veux faire deux classes qui se référencent mutuellement, tu peux les déclarer comme telles sans définir leur contenu :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A;
    class B;
     
    class A
    {
        public:
            B * b;
    };
     
    class B
    {
        public:
            A * a;
    };
    C'est parfois nécessaire, mais c'est généralement aller au devant d'ennuis pour pas grand chose.

  3. #3
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Merci pour ta réponse, maintenant ça marche très bien^^

    Le problème n'était pas de mettre une valeur par défaut, mais de faire en sorte qu'il puisse ne pas y avoir de valeur.

    Par exemple si FONCTION1 est : void (*)(void) je vais avoir un problème lors de la compilation car j'ai dans une fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    DONNEES1 d1
    FONCTION1 f1
    CLASSE1 c1 (ici (Principal *))
    (c1->*f1)(d1);
    De même si la fonction f1 n'existe pas (pas de fonction lancée lors du clique).
    Ou si la classe c1 n'existe pas (idem, pas de fonction lancée lors du clique).

  4. #4
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 485
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 485
    Par défaut
    Il y a deux cas à distinguer :

    1. Les paramètres template par défaut

    Malgré ce que tu es en train de faire, c'est le cas le plus approprié dans le contexte qui t'intéresse :

    Code C++ : 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
    #include <iostream>
     
    using namespace std;
     
    template <class A, class B, class C=char>
    class Toto
    {
        public:
            void affiche ();
    };
     
    template <class A, class B, class C>
    void Toto<A,B,C>::affiche ()
    {
        cout <<    "A: " << sizeof (A)
             << " - B: " << sizeof (B)
             << " - C: " << sizeof (C)
             << endl;
    }
     
    int main (void)
    {
        Toto<int,int>      x;
        Toto<int,int,int>  y;
     
        x.affiche();
        y.affiche();
     
        return 0;
    }

    Code Shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    $ ./programme
    A: 4 - B: 4 - C: 1
    A: 4 - B: 4 - C: 4

    Dans cet exemple, on voit bien que l'on a réussi à instancier deux variantes de la même classe, avec une seule et même définition. Dans le corps de main(), je passe la première fois deux paramètres template et, la seconde fois, trois paramètres template.

    Dans le résultat, quand on examine la taille de C, on voit bien que dans le premier cas, c'est un char qui a été choisi par défaut, comme spécifié et que, dans le second cas, c'est bien la largeur d'un int qui apparaît.

    Une valeur « par défaut » n'est pas simplement une valeur initiale. C'est bien la valeur à utiliser lorsque le paramètre « fait défaut ».



    2 La spécialisation partielle

    L'idée est de faire une spécialisation, c'est-à-dire une écriture de code spécifique à un type donné, mais en spécifiant certains paramètres seulement, et en laissant les autres génériques.

    Cela dit, si tu veux faire une spécialisation partielle d'une classe, tu ne pourras pas spécialiser directement ces fonctions-membres : il te faudra ré-écrire une définition de classe spécialisée. Voici un exemple qui mêle paramètre par défaut et spécialisation partielle :

    Code C++ : 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
    #include <iostream>
     
    using namespace std;
     
    template <class A,class B,class C,class D=char>
    class Exemple
    {
        public:
            void id();
    };
     
    template <class B,class C,class D>
    class Exemple<float,B,C,D>
    {
        public:
            void id();
    };
     
    template <class B,class C,class D>
    void Exemple<float,B,C,D>::id()
    {
        cout << "Classe Exemple spécial float" << endl;
     
        cout << "  A: " << "f"
             << "  B: " << sizeof (B)
             << "  C: " << sizeof (C)
             << "  D: " << sizeof (D)
             << endl;
    }
     
    template <class A,class B,class C,class D>
    void Exemple<A,B,C,D>::id()
    {
        cout << "Classe Exemple général" << endl;
     
        cout << "  A: " << sizeof (A)
             << "  B: " << sizeof (B)
             << "  C: " << sizeof (C)
             << "  D: " << sizeof (D)
             << endl;
    }
     
    int main (void)
    {
        Exemple<float,float,float,float>       x;
        Exemple<float,float,float>             y;
        Exemple<char,char,char>                z;
        Exemple<char,char,char,int>            i;
        Exemple<char,char,char,float>          j;
        Exemple<double,double,double>          a;
        Exemple<double,double,double,double>   b;
     
        x.id();
        y.id();
        z.id();
        i.id();
        j.id();
        a.id();
        b.id();
     
        return 0;
    }

    Code Shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ ./programme
    Classe Exemple spéciale float
      A: f  B: 4  C: 4  D: 4
    Classe Exemple spéciale float
      A: f  B: 4  C: 4  D: 1
    Classe Exemple générale
      A: 1  B: 1  C: 1  D: 1
    Classe Exemple générale
      A: 1  B: 1  C: 1  D: 4
    Classe Exemple générale
      A: 1  B: 1  C: 1  D: 4
    Classe Exemple générale
      A: 8  B: 8  C: 8  D: 1
    Classe Exemple générale
      A: 8  B: 8  C: 8  D: 8

    On constate plusieurs choses :
    • On a pu, dans main(), instancier « Exemple » avec trois ou quatre paramètres, mais uniquement grâce au paramètre par défaut. Si tu retires le « =char » de la ligne 5, le programme ne compile plus ;
    • La spécialisation partielle m'a permis de définir, en plus du code général pour tous les cas non explicitement pris en charge, de définir du code pour tous les cas où le premier paramètre template est float, quels que soient les autres ;
    • Pour cela, il m'a fallu spécialiser la fonction-membre id() mais, celle-ci faisant partie d'une classe qui, ELLE, est template, il fallait d'abord que je spécialise la classe entière, pour pouvoir ensuite définir une fonction-membre spéciale, appartenant à cette classe spécialisé. J'ai donc deux définitions de la même classe et deux définitions de la même fonction-membre ;
    • « template <> » n'est pas une signature. C'est juste un prélude à une définition quelconque qui va enrichir le langage nécessaire pour la décrire ;
    • Quelque soit les spécialisations que l'on fait et le nombre de paramètres template restant ou non à spécifier, c'est toujours la classe originale qui fait foi et le nombre de paramètres à lui passer reste le même.


    Pour résumer tout cela :

    • Ce sont les paramètres par défaut qui vont te permettre d'en omettre par la suite si tu le souhaites ;
    • Si tu fais une spécialisation de classe, il faut en fait spécialiser la classe elle-même, puis écrire les fonctions-membres de chacune de ces spécialités, mais spécialiser directement les fonctions-membres.

  5. #5
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 026
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 026
    Par défaut
    Je vois, merci pour ton aide.

    Sinon, une personne m'a conseillée de créer une classe "nothing" qui servira au bouton d'appeler des fonctions ne faisant rien.

    J'ai tout de même 2 questions :

    Pour un template<typename A=int, typename B=int, typename C=int>, comment fait-on pour mettre le type B par défaut tout en spécifiant un type A et C?



    Dans le cas d'argument vide, quel type X utiliser?

    Car il faudra que je spécialise une classe pour dire que l'argument est vide quand le type sera X.

    Mais il ne faudrait pas que l'utilisateur, en utilisant le type X appelle sa fonction sans argument alors qu'elle en nécessite.

    Un type "void" est-il possible (au pire je testerais ce soir)

    EDIT : je suis en train de me demander si les typedef int Mon_type sont considéré comme des types à part entière par les templates (je testerais ce soir)

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 485
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 485
    Par défaut
    Citation Envoyé par Neckara Voir le message
    Sinon, une personne m'a conseillée de créer une classe "nothing" qui servira au bouton d'appeler des fonctions ne faisant rien.
    Ça me paraît douteux comme principe. Pourrais-tu nous ré-expliquer au propre ce que tu comptes faire exactement et la chemin que tu comptes suivre ? Parce que présenté comme ça, il semble que tu aies posé tes fondations sur du sable et que tu essaies d'utiliser des artifices a posteriori pour pallier les uns après les autres les problèmes qui surgissent.

    Ce n'est absolument pas une critique, c'est très fréquent en programmation et c'est critique en C++ parce qu'il est très facile de tomber dans ces travers avec ce langage. Je dirais même qu'architecturer proprement un programme dès le départ avec un outil aussi puissant demande tant de savoir-faire que c'est devenu un métier en soi.

    J'ai tout de même 2 questions : Pour un template<typename A=int, typename B=int, typename C=int>, comment fait-on pour mettre le type B par défaut tout en spécifiant un type A et C?
    On ne peut pas. Lorsque tu spécifies un type par défaut, tous les arguments suivants, s'ils existent, doivent également disposer du leur. Présenté autrement : tous les paramètres disposant d'un argument par défaut doivent être tassés sur la droite.

    Ce principe est aussi valable pour les arguments de toutes les fonctions, qu'elles soient template ou pas.

    De la même façon, lorsque tu invoques ta fonction, si tu spécifes un argument, tous les autres avant lui doivent l'être aussi, et si tu l'ignores, tous les autres après lui doivent l'être aussi.

    C'est nécessaire car tu ne peux pas laisser un trou dans une liste d'argument : « a,b,,,e,f ». C'était possible en BASIC, pas en C ou C++, ne serait-ce que parce que ces arguments sont censés aller dans la pile et pouvoir être récupérés à emplacement fixe. Sinon, il faudrait utiliser des méta-données à côté pour savoir si un argument a été effectivement transmis ou pas.

    Autrement, si tu as plusieurs arguments « par défaut » et que ne spécifie que certains d'entre eux, le compilateur ne peut pas savoir quels arguments ont été spécifiés et quels arguments ont été laissés en blanc dans ta liste.

    Dans le cas d'argument vide, quel type X utiliser?
    La réponse est ci-dessus : il n'y a pas d'argument vide.
    Pour les arguments non spécifiés, tu as le droit de spécifier un type void par défaut, pour peu que cela ait du sens syntaxiquement : ton type template va être substitué à ton code comme si tu l'avais toi-même tapé en toutes lettres à cet endroit, puis compilé.

    Car il faudra que je spécialise une classe pour dire que l'argument est vide quand le type sera X.

    Mais il ne faudrait pas que l'utilisateur, en utilisant le type X appelle sa fonction sans argument alors qu'elle en nécessite.
    Si tu spécialises une classe et que tu redéfinis une fonction existante mais sans argument, tu changes de signature et ce sera considéré comme une surcharge : la fonction avec argument sera disponible dans toutes les versions de ta classe et la version sans argument sera utilisable, en plus, dans ta spécialité X.

    Mais je maintiens que ce n'est probablement pas la bonne approche à ton problème.

    EDIT : je suis en train de me demander si les typedef int Mon_type sont considéré comme des types à part entière par les templates (je testerais ce soir)
    Oui. Un typedef est un alias de nom de type. Tu as le droit de l'utiliser si le type original utilisé dans ton typedef passerait lui-aussi.

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

Discussions similaires

  1. Réponses: 19
    Dernier message: 23/12/2009, 19h22
  2. Réponses: 3
    Dernier message: 09/04/2009, 11h30
  3. Template / Classe Latex
    Par zifox dans le forum Débuter
    Réponses: 3
    Dernier message: 26/03/2009, 18h04
  4. template<class> et template<typename>
    Par mister3957 dans le forum C++
    Réponses: 10
    Dernier message: 01/11/2007, 09h32
  5. Héritage classe template->classe template
    Par zabibof dans le forum Langage
    Réponses: 5
    Dernier message: 11/08/2007, 11h05

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