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 :

Problème de class template


Sujet :

Langage C++

  1. #1
    Candidat au Club
    Problème de class template
    Bonjour,

    j’écris un code pour arduino. J'ai un problème avec une class template. J'immagine que j'ai oublié quelque chose dans l'écriture de ma class template, mais je sèche depuis hier... Pouvez vous jeter un coup d’œil pour m'aider?
    Merci beaucoup!

    le message d'erreur:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [Tue Aug 30 16:01:17 2016] Processing megaatmega2560 (platform: atmelavr, board: megaatmega2560, framework: arduino)
    --------------------------------------------------------------------------------
    avr-g++ -o .pioenvs/megaatmega2560/src/main.o -c -fno-exceptions -fno-threadsafe-statics -std=gnu++11 -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega2560 -DF_CPU=16000000L -DPLATFORMIO=021102 -DARDUINO_ARCH_AVR -DARDUINO_AVR_MEGA2560 -DARDUINO=10608 -I.pioenvs/megaatmega2560/FrameworkArduino -I.pioenvs/megaatmega2560/FrameworkArduinoVariant -I.pioenvs/megaatmega2560/Broches -I .pioenvs/megaatmega2560/SPI -I.pioenvs/megaatmega2560/PrintPGM -I.pioenvs/megaatmega2560/Adafruit_GFX_v1.0.2_NB8bits -I.pioenvs/megaatmega2560/PCD8544 -I.pioenvs/megaatmega2560/Clavier -I.pioenvs/megaatmega2560/IO_PCD8544_3touches -I.pioenvs/megaatmega2560/Widgets -Isrc src/main.cpp
    avr-g++ -o .pioenvs/megaatmega2560/firmware.elf -Os -mmcu=atmega2560 -Wl,--gc-sections,--relax .pioenvs/megaatmega2560/src/main.o -L/hom/thom/.platformio/packages/ldscripts -L.pioenvs/megaatmega2560 -Wl,--start-group .pioenvs/megaatmega2560/libFrameworkArduinoVariant.a .pioenvs/megaatmega2560/libFrameworkArduino.a -lm .pioenvs/megaatmega2560/libBroches.a .pioenvs/megaatmega2560/libSPI.a .pioenvs/megaatmega2560/libPrintPGM.a .pioenvs/megaatmega2560/libAdafruit_GFX_v1.0.2_NB8bits .pioenvs/megaatmega2560/libPCD8544.a .pioenvs/megaatmega2560/libClavier.a .pioenvs/megaatmega2560/libIO_PCD8544_3touches.a .pioenvs/megaatmega2560/libWidgets.a -Wl,--end-group
    .pioenvs/megaatmega2560/src/main.o: In function `loop':
    /home/thom/Arduino/test_GFX/src/main.cpp:27: undefined reference to `Widgets<IO_PCD8544_3touches_SPI_software>::menuListeOuiNon(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char)'
    .pioenvs/megaatmega2560/src/main.o: In function `__static_initialization_and_destruction_0':
    /home/thom/Arduino/test_GFX/src/main.cpp:7: undefined reference to `Widgets<IO_PCD8544_3touches_SPI_software>::Widgets(IO_PCD8544_3touches_SPI_software&)'
    collect2: error: ld returned 1 exit status
    scons: *** [.pioenvs/megaatmega2560/firmware.elf] Error 1


    le code du fichier main.cpp:
    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
    #include <Arduino.h>
    #include <IO_PCD8544_3touches.h>
    #include <Widgets.h>
    
    IO_PCD8544_3touches_SPI_software display=IO_PCD8544_3touches_SPI_software(BROCHE_ECRAN_SCLK, BROCHE_ECRAN_SDIN, BROCHE_ECRAN_DC, BROCHE_ECRAN_CS, BROCHE_ECRAN_RESET); // Software SPI (slower updates, more flexible pin options):
    
    Widgets<IO_PCD8544_3touches_SPI_software> widgets(display);
    
    void setup(){
        display.begin();
        display.setContrast(50);
        display.display();
        display.setTextColor(BLACK, WHITE);
        display.setTextSize(1);
    }
    
    void loop(){
      uint8_t touche;
      display.aucuneTouche();
    
      touche=display.getTouche(-1);
      display.println(millis());
      display.print(touche,BIN);
      display.display();
      display.aucuneTouche();
    
      widgets.menuListeOuiNon();
      display.clearDisplay();
    }


    le code du header de la librairie concernée: (Widgets.h)
    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
    #ifndef __WIDGETS_H__
    #define __WIDGETS_H__
     
    #include <IO_PCD8544_3touches.h>
    struct retourSelect {
      uint8_t touche;
      uint8_t result;
    };
     
    template<typename T>
    class Widgets{
    public:
      //Widgets();
      Widgets(T& _display);
     
      retourSelect menuListe(char* chaine, uint8_t nb, uint8_t taille = 15, uint8_t yMin = 0, uint8_t yMax = LCDHEIGHT, uint8_t xMin = 0, uint8_t xMax = LCDWIDTH, uint8_t hLigne = 1, uint8_t depart = 0, uint8_t textSize = 1, uint8_t actual = 0, uint8_t permissions = 0b10010000); // pour compatibilité avec l'ancienne forme
      retourSelect menuListeV2(void (*callback)(uint8_t, char*, uint8_t, void*), void* arg, uint8_t nb, uint8_t taille = 15, uint8_t yMin = 0, uint8_t yMax = LCDHEIGHT, uint8_t xMin = 0, uint8_t xMax = LCDWIDTH, uint8_t hLigne = 1, uint8_t depart = 0, uint8_t textSize = 1, uint8_t actual = 0, uint8_t permissions = 0b10010000);
      retourSelect menuListeOuiNon(uint8_t yMin = 0, uint8_t yMax = LCDHEIGHT, uint8_t xMin = 0, uint8_t xMax = LCDWIDTH, uint8_t hLigne = 1, uint8_t depart = 0, uint8_t textSize = 1, uint8_t permissions = 0b10010000);
    protected:
      T* display;
    };
     
    void _creeLigneMenuListeV1(uint8_t i, char* chaine, uint8_t taille, void* arg);
    void _creeLigneMenuListeOuiNon(uint8_t i, char* chaine, uint8_t taille, void* arg);
    #endif


    et le code de la librairie (Widgets.cpp):
    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
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    #include "Widgets.h"
    
    template<typename T>
    Widgets<T>::Widgets(T& _display){
      display=&_display;
    }
    
    template<typename T>
    retourSelect Widgets<T>::menuListeOuiNon(uint8_t yMin, uint8_t yMax, uint8_t xMin, uint8_t xMax, uint8_t hLigne, uint8_t depart, uint8_t textSize, uint8_t permissions) {
      return menuListeV2(display, _creeLigneMenuListeOuiNon, (void*)&yMin, 2, 4, yMin, yMax, xMin, xMax, hLigne, depart, textSize, 0, permissions);
    }
    
    template<typename T>
    retourSelect Widgets<T>::menuListe(char* chaine, uint8_t nb, uint8_t taille, uint8_t yMin, uint8_t yMax, uint8_t xMin, uint8_t xMax, uint8_t hLigne, uint8_t depart, uint8_t textSize, uint8_t actual, uint8_t permissions) {
      /*   affiche une liste est retourne le numéro de l'item selectionné
            liste est un tableau de pointeur vers des chaine comprenant la chaine de caractère de l'item
            nb est le nombre d'élément dans la liste principale
            taille donne la taille des élements de la chaine /!\ y compris le bit de fin de chaine /!\
            yMin est le point de départ de la liste en graphique (par défaut 0)
            yMax est le bas de la liste (par défaut bas de l'écran)
            xMin ... idem en X
            xMax ... idem en X
            hLigne est la hauteur de la ligne en nb de caractère (càd 1 car = 8 pixels de haut), par defaut 1
            permissions indique les permissions pour quitter le menu (autre que sélection), par défaut TIMEOUT & BT_ESC
    
            /!\ si plusieurs lignes pour une chaine (càd hLigne!=0) compter un caractère en plus pour chaque ligne pour ajouter une fin de ligne
      */
    
      return menuListeV2(display, _creeLigneMenuListeV1, (void*)chaine, nb, taille, yMin, yMax, xMin, xMax, hLigne, depart, textSize, actual, permissions);
    }
    
    template<typename T>
    retourSelect Widgets<T>::menuListeV2( void (*callback)(uint8_t, char*, uint8_t, void*), void* arg, uint8_t nb, uint8_t taille, uint8_t yMin, uint8_t yMax, uint8_t xMin, uint8_t xMax, uint8_t hLigne, uint8_t depart, uint8_t textSize, uint8_t actual, uint8_t permissions) {
      /*   affiche une liste est retourne le numéro de l'item selectionné
            liste est un tableau de pointeur vers des chaine comprenant la chaine de caractère de l'item
            nb est le nombre d'élément dans la liste principale
            taille donne la taille des élements de la chaine /!\ y compris le bit de fin de chaine /!\
            yMin est le point de départ de la liste en graphique (par défaut 0)
            yMax est le bas de la liste (par défaut bas de l'écran)
            xMin ... idem en X
            xMax ... idem en X
            hLigne est la hauteur de la ligne en nb de caractère (càd 1 car = 8 pixels de haut), par defaut 1
            permissions indique les permissions pour quitter le menu (autre que sélection), par défaut TIMEOUT & BT_ESC
    
            /!\ si plusieurs lignes pour une chaine (càd hLigne!=0) compter un caractère en plus pour chaque ligne pour ajouter une fin de ligne
      */
    #ifdef DEBUG
      Serial.print("menuListeV2(callback@");
      Serial.print((uint16_t)callback, HEX);
      Serial.print(", arg@");
      Serial.print((uint16_t)arg, HEX);
      Serial.print(", nb=");
      Serial.print(nb);
      Serial.print(", taille=");
      Serial.print(taille);
      Serial.print(", yMin=");
      Serial.print(yMin);
      Serial.print(", yMax=");
      Serial.print(yMax);
      Serial.print(", xMin=");
      Serial.print(xMin);
      Serial.print(", xMax=");
      Serial.print(xMax);
      Serial.print(", hLigne=");
      Serial.print(hLigne);
      Serial.print(", depart=");
      Serial.print(depart);
      Serial.print(", textSize=");
      Serial.print(textSize);
      Serial.print(", actual=");
      Serial.print(actual);
      Serial.print(", permissions=0b");
      Serial.print(permissions, BIN);
      Serial.println(");");
    #endif
    
      //uint8_t tailleLigne;
      uint8_t largCarac = (textSize == 0) ? 4 : (6 * textSize);
      uint8_t hautCarac = (textSize == 0) ? 6 : (8 * textSize);
    
      taille = min(taille, ((xMax - xMin)  / largCarac + 1) * hLigne); // taille de la ligne en caractères
      //if (hLigne == 1)tailleLigne = taille;
      //else tailleLigne = (xMax - xMin) / largCarac + 1;
    #ifdef DEBUG
      Serial.print("taille:");
      Serial.print(taille);
    #endif
    
      uint8_t touche, pos, deb;
      uint8_t nbLigneVisible = min(((yMax - yMin) / (hautCarac * hLigne) ), nb); // nombre de ligne visible à la fois
      uint8_t derniereLigne = (((yMax - yMin) % (hautCarac * hLigne)  == 0) || (nb == nbLigneVisible)) ? 0 : 1;
      permissions &= 0b11110000; // pas de permissions pour les 4 1ere touche ( BT_SELECT,BT_HAUT,BT_BAS)
      uint16_t colorT = BLACK, colorF = WHITE;
      char chaine[22];
    
    
      // ajout depart
      if (depart == 0) {
        pos = 0;
        deb = depart;
      } else if ( depart <= nb - nbLigneVisible) {
        pos = 1;
        deb = depart - 1;
      } else {
        deb = nb - nbLigneVisible;
        pos = depart - deb;
      }
      display->setTextSize(textSize);
      for (; { // boucle infini complète
        // efface la zone d'affichage
        display->fillRect(xMin, yMin, xMax, yMax, WHITE);
        for (uint8_t i = 0; i < (nbLigneVisible + derniereLigne); ++i) {
    
          display->callback(i + deb, chaine, taille, arg);
    
          for (uint8_t j = 0; j < hLigne; ++j) {
            display->setCursor(xMin, yMin + (i * hLigne + j) * hautCarac);
            uint8_t hexa = 0;
            for ( uint8_t k = 0; k < taille && k < 22; ++k) {
              char c = chaine[k];
              if ( c == 0 && hexa == 0) break;
              if ( c == -1) {
                ++k;
                hexa = chaine[k];
              } else {
                if ( hexa != 0) {
                  if ( (uint8_t)c < 16) display->print(0);
                  display->print((uint8_t)c, HEX);
                  --hexa;
                } else {
                  display->print(c);
                }
              }
            }
          }
        }
        display->invertRect(xMin, yMin + (pos * hLigne) * hautCarac, xMax - xMin, hLigne * hautCarac);
    
        if (deb + nbLigneVisible == nb) display->fillRect(xMin, yMin + nbLigneVisible * hautCarac * hLigne, largCarac * (taille - 1), yMax - yMin - nbLigneVisible * hautCarac * hLigne, colorF);
        display->display();
        for (; { // boucle infini gestion touche
          display->aucuneTouche();
          if ( actual == 0) touche = display->getTouche(-1); // cas normal, pas d'actualisation
          else {
            touche = display->getTouche(actual);
            if ( touche == TIMEOUT  && millis() - display->getDateDerniereTouche() < 60000 ) {
              touche = 0; // si TIMEOUT mais qu'on n'a pas atteint l'extinction de l'ecran -> touche = 0
              break;
            }
          }
          if ( ((touche == TIMEOUT) && ((permissions & TIMEOUT) != 0)) || ((touche == (BT_HAUT | BT_BAS)) && ((permissions & BT_ESC) != 0)) || ((touche == (BT_HAUT | BT_BAS | BT_SELECT)) && ((permissions & BT_MENU) != 0)) || (touche == BT_SELECT)) break;
    
          if (touche == BT_BAS || touche == BT_HAUT) {
            display->invertRect(xMin, yMin + (pos * hLigne) * hautCarac, xMax - xMin, hLigne * hautCarac);
            if (touche == BT_BAS) {
              if ((pos == nbLigneVisible - 1) || (((deb + nbLigneVisible) != nb) && pos == nbLigneVisible - 2)) break;
              ++pos;
            } else { // touche == BT_HAUT
              if  ((pos == 0) || ((deb != 0 && pos == 1))) break;
              --pos;
            }
            display->invertRect(xMin, yMin + (pos * hLigne) * hautCarac, xMax - xMin, hLigne * hautCarac);
            display->display();
          }
        } // fin boucle infini gestion touche
        if ( touche != 0) {
          if (touche != BT_BAS && touche != BT_HAUT) break;
          if (touche == BT_BAS) {
            if (pos == nbLigneVisible - 1) {
              pos = 0;
              deb = 0;
            } else ++deb;
          } else { // touche BT_HAUT
            if (pos == 0) {
              pos = nbLigneVisible - 1;
              deb = (nbLigneVisible < nb) ? nb - nbLigneVisible : 0;
            }
            else --deb;
          }
        }
      } // fin boucle infinie complète
      if (touche == 0) touche = TIMEOUT;
      if (touche == BT_SELECT) touche = 0;
      display->invertRect(xMin, yMin + (pos * hLigne) * hautCarac, xMax - xMin, hLigne * hautCarac);
      display->display();
    
      retourSelect result;
      result.result = pos + deb;
      result.touche = touche;
      display->setTextSize(1);
      return result;
    }
    
    void _creeLigneMenuListeV1(uint8_t i, char* chaine, uint8_t taille, void* arg) {
      char* chaineCom = (char*)arg;
      strcpy(chaine, &chaineCom[i * taille]);
      return;
    }
    
    void _creeLigneMenuListeOuiNon(uint8_t i, char* chaine, uint8_t taille, void* arg) {
      switch (i) {
        case 0:
          //strcpy_P(chaine, TXT_OUI);
          strcpy(chaine,"OUI");
          break;
        case 1:
          //strcpy_P(chaine, TXT_NON);
          strcpy(chaine,"NON");
          break;
        default:
          break;
      }
    }
    
    //template<typename T>
    //Widgets<T>::Widgets(){
    //  display=NULL;
    //}
    kollibar

    réveil le viking qui est en toi!!!!

  2. #2
    Expert confirmé
    Bonjour,

    Les définitions d'un template doivent être visibles d'un point où on veut l'instancier.
    Ici les définitions ne sont pas vues lors de la compilation de main, donc les template ne sont pas générés.
    Toutes les définitions d'un template sont normalement défini dans un fichier header, si dans un cpp, il faut forcer l'instanciation sinon il y aura erreur à l'édition des liens.

  3. #3
    Candidat au Club
    Bonsoir,
    OK, merci!

    Du coup, j'ai copié le contenu du .cpp dans le .h.

    Par contre, je ne comprends pas comment ça marche de forcer l’instanciation des template ?
    kollibar

    réveil le viking qui est en toi!!!!

  4. #4
    Expert éminent sénior
    Citation Envoyé par kollibar Voir le message
    Bonsoir,
    OK, merci!

    Du coup, j'ai copié le contenu du .cpp dans le .h.

    Par contre, je ne comprends pas comment ça marche de forcer l’instanciation des template ?
    En fait, le terme exact est "instanciation expliicte"...

    L'idée est de faire en sorte que le compilateur soit obligé de générer le code (binaire) correspondant à une spécialisation complète de ta classe (ou de ta fonction) template, par exemple, sous une forme proche de
    fichier .h
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <typename T>
    class MaClasse{
    public :
    void foo();
        /*on se fout pas mal de ce qu'il y a ici */
    };

    fichier.impl
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    /* on peut effectivement décider de fournir l'implémentation dans un autre fichier
     * on lui donnera alors n'importe quelle extension, en veillant quand même à ce qu'elle ne prête pas à confusion
     */
    template <typename T>
    void MaClasse<T>::foo(){
        /* du code
    }

    puis, dans un fichier d'implémentation, on veillera à regrouper les deux sous une forme proche de
    fichier.cpp
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    /* d'abord, la déclaration */
    #include <fichier.h>
    /* puis l'implémentation (si elle est séparée de la déclaration */
    #include <fichier.impl>
    /* et, enfin, on force le compilateur à générer le code binaire pour
     * les spécialisations qui nous intéressent
    template <> class MaClasse<int>; /* par exemple, pour le type int */
    template class MaClasse<std::string>; /* ou pour une chaine de caractères */
    template class MaClass<NimporteQuoi>; /* ou pour n'importe quel type de notre cru */
    De cette manière, tu a la certitude que le code binaire correspondant à la classe sera généré pour -- selon mon exemple -- le type int, le type std::string et le type NimporteQuoi en n'incluant que fichier.h

    Et si, d'aventure, tu venais à avoir besoin d'une spécialisation pour un autre type, il te "suffirait" d'inclure également fichier.impl dans le fichier dans lequel tu as besoin de cette spécification particulière

    PS : La technique est régulièrement utilisée lorsque tu décide de créer une bibliothèque, et que tu veux forcer l'existence du code binaire pour des types bien particuliers (qui seront -- le plus souvent -- les seuls types réellement utilisables avec ta classe template )
    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

  5. #5
    Candidat au Club
    Super, merci beaucoup !!
    kollibar

    réveil le viking qui est en toi!!!!

###raw>template_hook.ano_emploi###