Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème d'Inclusion Librairie Type adaptatif

Sujet résolu
    17 janvier 2023 à 9:29:33

    Bonjour, je suis un élève de BTS et nous travaillons actuellement sur un Tp qui concerne les type adaptatif (template).

    Notre professeur nous à fourni un exemple de code à utilisé avec un template, rapidement il s'agit d'une pile qui empile et dépile des valeurs avec un type adaptatif justement.

    Le souci est que lorsque nous essayons de répartir le programme avec le main, l'en-tête de la classe Pile et le code source de la classe Pile cela nous donne des erreurs comme :

    undifined references to 'Pile<char>::Pile()'

    Et pourtant le programme semble correct puisque qu'il fonctionne si l'ensemble du code fonctionne si il est mis dans un seul fichier main.

    D'après mon professeur cela est normal et dû aux types adaptatifs en eux mêmes, il ne sait pas comment l'expliquer mais je suis malgré tout curieux de savoir pourquoi :o.

    Si quelqu'un peut m'éclairer sur le sujet je suis preneur, j'en profite pour montrer le code et n'hésiter pas si vous voulez des précisions.

    Le main

    //le main
    
    #include <iostream>
    #include "Pile.h"
    using namespace std;
    
    
    int main()
    {
        Pile<char> pilec; // pile de caractères
        pilec.empile('a');
        pilec.empile('b');
        pilec.empile('c');
        cout << pilec.depile() << endl;
        cout << pilec.depile() << endl;
        cout << pilec.depile() << endl;
        Pile<int> pilei; // pile d'entiers
        pilei.empile(1);
        pilei.empile(2);
        pilei.empile(3);
        cout << pilei.depile() << endl;
        cout << pilei.depile() << endl;
        cout << pilei.depile() << endl;
    }

     Le fichier d'en-tête

    //Le Pile.h
    
    #ifndef PILE_H
    #define PILE_H
    #include <iostream>
    using namespace std;
    
    template<typename TYPE>
    class Pile
    {
        public:
            Pile();
            TYPE depile();
            void empile(TYPE);
        private:
           int sommet;
           TYPE tab[100];
    };
    
    #endif // PILE_H

     Le fichier source

    //Le Pile.cpp
    
    #include "Pile.h"
    #include <iostream>
    using namespace std;
    
    template<typename TYPE>
    Pile<TYPE>::Pile()
    {
        sommet = 0;
        //ctor
    }
    
    template<typename TYPE>
    void Pile<TYPE>::empile(TYPE val){
        tab[sommet] = val;
        sommet++;
    }
    
    template<typename TYPE>
    TYPE Pile<TYPE>::depile(){
        sommet--;
        TYPE tmp = tab[sommet];
        return tmp;
    }

     L'ensemble du code fourni

    #include <iostream>
    using namespace std;
    
    template<typename TYPE>
    class Pile{
    private:
        int sommet;
        TYPE tab[100];
    
    public:
        TYPE depile();
        void empile(TYPE);
        Pile();
    };
    template<typename TYPE>
    Pile<TYPE>::Pile(){
        sommet = 0;
    }
    
    template<typename TYPE>
    void Pile<TYPE>::empile(TYPE val){
        tab[sommet] = val;
        sommet++;
    }
    
    template<typename TYPE>
    TYPE Pile<TYPE>::depile(){
        sommet--;
        TYPE tmp = tab[sommet];
        return tmp;
    }
    // programme principal qui utilise les templates ///////////////////////////
    int main(){
        Pile<char> pilec; // pile de caractères
        pilec.empile('a');
        pilec.empile('b');
        pilec.empile('c');
        cout << pilec.depile() << endl;
        cout << pilec.depile() << endl;
        cout << pilec.depile() << endl;
        Pile<int> pilei; // pile d'entiers
        pilei.empile(1);
        pilei.empile(2);
        pilei.empile(3);
        cout << pilei.depile() << endl;
        cout << pilei.depile() << endl;
        cout << pilei.depile() << endl;
    }
    






    -
    Edité par Rhonas 17 janvier 2023 à 9:31:11

    • Partager sur Facebook
    • Partager sur Twitter

    Rien que ranger mon bureau me redonne 100 points de motivation

      17 janvier 2023 à 10:24:30

      Bonjour,

      Oui, on ne peut pas séparer .hpp et .cpp pour les template. En effet le compilateur doit connaitre la définition complète de la fonction ou de la classe pour spécialiser. Le mieux est de mettre toute ta classe (déclaration et définition) dans un .hpp que tu peux inclure dans le main.

      Si vraiment tu tiens à conserver la séparation déclaration/définition, tu peux simuler comme cela : déclarations dans un .hpp comme d'habitude, et définition dans un ".tpp" que tu inclues sous les déclarations de ton .hpp.

      Pile.hpp

      #ifndef PILE_H
      #define PILE_H
      #include <iostream>
      using namespace std;
       
      template<typename TYPE>
      class Pile
      {
          public:
              Pile();
              TYPE depile();
              void empile(TYPE);
          private:
             int sommet;
             TYPE tab[100];
      };
      
      #include "Pile.tpp"
      
      #endif

      Pile.tpp

      template<typename TYPE>
      Pile<TYPE>::Pile()
      {
          sommet = 0;
          //ctor
      }
       
      template<typename TYPE>
      void Pile<TYPE>::empile(TYPE val){
          tab[sommet] = val;
          sommet++;
      }
       
      template<typename TYPE>
      TYPE Pile<TYPE>::depile(){
          sommet--;
          TYPE tmp = tab[sommet];
          return tmp;
      }


      PS: Je laisse les autres maudire les tableaux à la C, les using namespace std, et l'enseignement en général du c++ dans le supérieur :)

      Edit : Je complète un peu. Le but de la séparation .hpp et .cpp est de pouvoir compiler séparément des morceaux de code. C'est intéressant car si tu modifies une partie d'un gros projet, tu n'as pas à tout recompiler. Mais on ne peut pas compiler du code template séparément sans connaitre sa spécialisation. Car au final, les fonctions générées par le compilateur à partir d'un code template sont comme les fonctions ordinaires, le compilateur doit connaitre les types précis utilisés au moment de la compilation (à l'exécution tous les types sont nécessairement déterminés). Donc compiler du code template à part, sans les instanciations précises avec les types concrets utilisés est absolument impossible et n'a aucun sens. En conséquence, la séparation .cpp .hpp n'a pas de sens non plus, sauf pour des questions de présentation...

      -
      Edité par Umbre37 17 janvier 2023 à 11:03:44

      • Partager sur Facebook
      • Partager sur Twitter
        17 janvier 2023 à 11:39:02

        Rebonjour alors, merci beaucoup Umbre37 pour tes explications c'était très instructif.

        Je ferme le topic et je retiens les explications pour aider ma classe :).
        • Partager sur Facebook
        • Partager sur Twitter

        Rien que ranger mon bureau me redonne 100 points de motivation

          17 janvier 2023 à 16:56:04

          Bonjour,

          Je rajouterais qu' avec la loi de 'one definition rule' on ne peut pas instancier de fonction template en 2 ou plus d'exemplaire pour un même type donné, dans ce cas les fonctions templates sont inlinées dans les fonctions où elles sont appelées ( c'est à dire que le code de la fonction est copié tel quel dans la fonction appelante ).

          • Partager sur Facebook
          • Partager sur Twitter

          Mon site web de jeux SDL2 entre autres : https://www.ant01.fr

            17 janvier 2023 à 22:21:48

            Les fonctions templates ne sont pas inlinés, elles suivent les mêmes règles que le spécificateur inline et la règle de l'ODR s'applique de la même manière qu'une fonction non template: autoriser plusieurs définitions identiques au moment du link. Si toutes les définitions ne sont pas pareil, il va y avoir un problème, mais aucune vérification du compilateur. La stratégie de gcc et clang (et probablement les autres) est de prendre la première définition trouvée comme référence.

            ODR => une définition par programme
            ODR + inline => une définition par TU

            Et pour préciser, utiliser inline ne fait pas en sorte que les fonctions soient inlinée, c'est juste un hint côté compilateur qui augmente la probabilité qu'elle le soit, ce n'est pas une obligation.

            • Partager sur Facebook
            • Partager sur Twitter
              18 janvier 2023 à 12:14:10

              Bonjour jo_link_noir

              Si je comprends bien ton explication dans le cas de "ODR + inline >  une définition par TU" , si il y a plusieurs fonctions identiques entre différentes translations units elles deviennent différentes dans leurs nom d'appel pour éviter de contredire la loi de ODR ?

              Egalement:

              "Si toutes les définitions ne sont pas pareilles, il va y avoir un problème" . Cela arrive si on utilise les concepts ? car il permettent d'adapter le corps de la fonction en fonction du(des) type(s) de la fonction  template ?

              -
              Edité par Warren79 18 janvier 2023 à 13:46:18

              • Partager sur Facebook
              • Partager sur Twitter

              Mon site web de jeux SDL2 entre autres : https://www.ant01.fr

                18 janvier 2023 à 23:22:39

                > si il y a plusieurs fonctions identiques entre différentes translations units elles deviennent différentes dans leurs nom d'appel pour éviter de contredire la loi de ODR ?

                Non, du tout, la fonction est dupliquée dans chaque TU et le linker n'en garde qu'une dans le programme final. C'est pour ça que s'il y a différentes implémentations entre TU, il va y avoir un problème puisqu'une seule sera utilisée.

                Par exemple (je mets tout dans des .cpp parce que l'exemple est plus simple, mais c'est pareil en séparant dans des .h)

                test1.cpp

                inline int foo() { return 1; }
                
                int g() { return foo(); }

                test2.cpp

                inline int foo() { return 2; }
                
                int f() { return foo(); }

                main.cpp

                #include <iostream>
                
                int f();
                int g();
                int foo();
                
                int main()
                {
                  std::cout << f() <<;
                  std::cout << g() <<;
                  std::cout << foo() <<;
                }

                Avec g++ test1.cpp test2.cpp main.cpp: 111
                Avec g++ test2.cpp test1.cpp main.cpp: 222

                Et le comportement va changer en fonction des flags utilisés et si le compilateur inline ou pas. Là, il ne fait pas d'inline parce que les optimisations ne sont pas activées. Avec cet exemple, si on ajoute -O1, il va y avoir une erreur de link parce qu'il ne trouve pas foo() dans main(). Mais si on vire l'appel de foo(), le résultat est alors 21.

                Par contre, si on enlève inline, le linker se termine en erreur parce qu'il y a 2 implémentations de foo().

                -
                Edité par jo_link_noir 18 janvier 2023 à 23:23:21

                • Partager sur Facebook
                • Partager sur Twitter
                  19 janvier 2023 à 9:01:06

                  Jo link noir a écrit [...]

                  Ok, je saisis l' idée; merci pour tes explications.

                  -
                  Edité par Warren79 19 janvier 2023 à 9:01:22

                  • Partager sur Facebook
                  • Partager sur Twitter

                  Mon site web de jeux SDL2 entre autres : https://www.ant01.fr

                  Problème d'Inclusion Librairie Type adaptatif

                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                  • Editeur
                  • Markdown