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 :

Héritage de classe template


Sujet :

Langage C++

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Février 2009
    Messages
    17
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 17
    Par défaut Héritage de classe template
    Bonjour à tous,

    je rencontre un petit soucis sur de l'héritage de classe "templatisée", et j'aimerais avoir votre avis face à un problème de link.
    Pour info:
    - je ne souhaite pas discuter de la légitimité de ce que je fais dans le code; même si vous trouvez que mon code est pourri/inutile, ça pose tout de même une question sur le langage et sa syntaxe
    - mon test fonctionne sur un compilo (Visual), mais pas sur un autre (compilateur C++ ARM d'IAR pour l'embarqué); mais il doit bien y avoir un truc normalisé C++03 là-dessus qui devrait passer sur compilo IAR (j'espère)
    - les compilos évoqués dans ce message sont censés suivre C++03. Pas C++11.


    Je dispose d'une classe Maman:
    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
     
    // Maman.cpp/.hpp
     
    template<int N>
    class Maman
    {
    public:
        Maman(const char* uneChaine);
        virtual ~Maman();
        virtual int GetValue();
    };
     
    template<int N>
    Maman<N>::Maman(const char* uneChaine)
    {
        // do something
    }
     
    template<int N>
    Maman<N>::~Maman()
    {}
     
    template<int N>
    int Maman<N>::GetValue()
    {
        return N;
    }
    J'implémente une classe Fille, héritée de Maman.
    Je trouve beaucoup d'exemples sur le web de classe fille elle-même templatisée. Ici ce n'est pas le cas: Fille n'est pas templatisée; mieux: elle précise de quelle "version" de Maman elle hérite (pitié, ne pas répondre des trucs du genre "ça sert à rien", ou "je ferais autrement"... le code ici est volontairement simplifié et idiot pour poser le problème technique et rechercher la solution adéquate):
    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
     
    // Fille.cpp/.hpp
     
    class Fille : public Maman<6>
    {
    public:
        Fille(const char* uneChaine);
        virtual ~Fille();
    };
     
    Fille::Fille(const char* uneChaine):
        Maman(uneChaine)
    {}
     
    Fille::~Fille()
    {}
    Bien entendu, j'instantie une Fille:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int main()
    {
        Fille maFille("une chaine");
        maFille.GetValue();
        return 0;
    }

    Quand je compile.... problèmes au link (compilo IAR):
    Error[Li005]: no definition for "Maman<(int)6>::Maman(const char*)"
    [referenced from Fille.obj]
    Error[Li005]: no definition for "Maman<(int)6>::~Maman()"
    [referenced from Fille.obj]
    Error[Li005]: no definition for "Maman<(int)6>::GetValue()"
    [referenced from Fille.obj]

    Alors comme je suis un bon gars, je me dis que je vais remettre le paramètre de template dans l'appel au constructeur de Maman présent dans la liste d'initialisation du constructeur de Fille
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Fille::Fille(const char* uneChaine):
        Maman<6>(uneChaine)
    {}

    Résultat: pas mieux.

    De toutes façons:
    1) même si ça avait changé quelque chose pour le constructeur, le problème serait resté entier pour le destructeur... (ça aurait peut être donné une piste)
    2) je serais étonné de devoir répéter la valeur du paramètre de template pour chaque méthode de Fille... et de toutes façons où est-ce que je pourrais le faire...?


    A noter que dans les 2 essais d'implémentation de Fille, Visual Studio réussi à linker et me générer mon exécutable.
    Avec le compilateur IAR, toujours cette satanée erreur.


    Avez vous une idée?

    Merci d'avance pour vos réponses!

    Nicolas

  2. #2
    Membre Expert
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Par défaut
    Personnellement, j’accuserai ton compilateur.

    Pour info, ton code passe très bien sur clang et gcc, et il passe avec l’appel explicite à Maman<6> sur un autre compilo arm (erreur de compilation sinon).

    Comme c’est une erreur de link, tu peux essayer de blouser ton compilateur. Déclare dans un fichier (par exemple, le fichier Fille.cpp) une fonction f de la sorte :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    void fille_private_f()
    {
       Maman<6> m;
       m.GetValue();
    }
    Note : je me doute que dans la réalité, ça va être plus merdique que ça, le nombre de fonctions étant beaucoup plus importants. Mais ça pourrait te permettre de remonter un bug à l’éditeur du compilo (je suis surpris que certains compilateurs aient encore des problèmes sur des cas aussi simples. Il s’agit d’une version récente ?).

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Février 2009
    Messages
    17
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 17
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Note : je me doute que dans la réalité, ça va être plus merdique que ça, le nombre de fonctions étant beaucoup plus importants. Mais ça pourrait te permettre de remonter un bug à l’éditeur du compilo (je suis surpris que certains compilateurs aient encore des problèmes sur des cas aussi simples. Il s’agit d’une version récente ?).
    Moi aussi je suis surpris, même si tout semble indiquer que c'est le compilo qui bug.
    C'est pour ça que j'avais besoin d'autres avis (je n'en croyais pas mes yeux!)

    Pour info, voici les infos de version du compilo:
    IAR ANSI C/C++ Compiler V5.40.0.51500/W32 for ARM
    Copyright (C) 1999-2009 IAR Systems AB.
    En tous cas, merci pour ta réponse et pour tes essais sur autres compilateurs!

  4. #4
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Je vois dans ton commentaire maman.cpp/hpp
    Tu n'as pas mis les définitions dans des fichiers .cpp séparés ?

    Si oui, il faut pas, voir la FAQ.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Une petite question avant de commencer...

    Quand, dans le code, tu mets le commentaire
    Doit-on bien comprendre ce que je comprends, à savoir que la définition de la classe maman est dans maman.hpp et que l'implémentation est dans maman.cpp

    Si c'est le cas, je tiens à te rappeler que, pour que les template fonctionnent, il faut que l'implémentation des fonctions soient accessibles au au compilateur lorsqu'il rencontre quelque chose qui les spécialise.

    Si tu obtiens une erreur à l'édition de liens (et que j'ai bien compris la signification du commentaire ), c'est normal :

    Quand tu définis une classe template, tu dis au compilateur quelque chose comme
    Je ne sais pas encore quel type de données je vais manipuler, mais je sais très bien comment je vais les manipuler
    Et comme le code binaire qui sera généré par le compilateur dépend du type de la donnée (parce que, pour le compilateur, un type est d'abord et avant tout une information de taille et de décalage à utiliser pour s'assurer que toutes les données sont accessibles), il attendra d'avoir l'information de type pour pouvoir générer le code binaire qui devra être exécuté.

    Lorsque tu utilise un entier comme paramètre template, tu dis au compilateur quelque chose comme
    Je ne sais pas encore quelle valeur ce sera, mais:
    • Ce sera une constante de compilation
    • Pour tout N que tu rencontreras, tu créeras un type différent de telle sorte que Maman<N-1> soit différent de Maman<N> et de Maman<N+1>
    Du coup, lorsque le compilateur rencontre le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<int N>
    int Maman<N>::GetValue()
    {
        return N;
    }
    Il a beau savoir ce qu'il doit faire, il lui manque quand même une information des plus importantes : quelle valeur doit il donner à N

    Et fatalement, cela l'empêche de générer le code binaire qui correspond, par exemple à Maman<6>.

    Le résultat des courses, c'est que, quand dans Fille, il va rencontrer la directive include <Maman.h>, il va copier le contenu du fichier Maman.h à savoir
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template<int N>
    class Maman
    {
    public:
        Maman(const char* uneChaine);
        virtual ~Maman();
        virtual int GetValue();
    };
    Du coup, vec le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    class Fille : public Maman<6>
    Il sait quelle valeur donner à N... Mais il ne dispose pas des instructions qui lui permettent de générer le code binaire pour les fonctions qu'il rencontre : Il sait juste qu'il existe un constructeur prenant une chaine de caractères, un destructeur virtuel et une fonction virtuelle qui s'appelle GetValeur, ce qui lui suffit pour vérifier les différents appels mais qui est insuffisant pour générer le code binaire correspondant.

    Au final, tu te retrouves avec d'un coté, un fichier Maman.cpp dans lequel le code binaire des fonctions membres n'a pas été généré (parce qu'il ne savait pas quelle valeur donner à N) et de l'autre, un fichier (simplifions Fille.cpp) dans lequel il aurait pu générer le code binaire correspondant, vu qu'il savait que N valait 6, à condition de savoir quel code il devait mettre en place. Or, il ne dispose pas de cette informaition

    Le compilateur n'est pas assez malin pour se rendre compte de cette situation parce qu'il "oublie" tout ce qu'il a fait à chaque fois qu'il change de fichier d'implémentation, et, pour lui, "tout est pour le mieux".

    C'est à ce moment là que l'éditeur de liens prend le relais, et il rencontre donc des symboles qui font référence à Maman<6>::GetValue() (par exemple).

    Il va donc chercher dans les différents fichiers objets (les fichiers qui contiennent le code binaire généré par le compilateur) après la partie de code binaire qui correspond à ce symbole. Mais comme il n'a jamais été généré, il ne va évidemment pas le trouver.

    L'éditeur de liens n'aura donc pas d'autre choix que de te dire qu'il ne l'a pas trouvé et de sortir sur ce message d'erreur

    En conclusion, et c'est peut être ce qui a surpris white_tentacle, le code
    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
    template<int N>
    class Maman
    {
    public:
        Maman(const char* uneChaine);
        virtual ~Maman();
        virtual int GetValue();
    };
     
    template<int N>
    Maman<N>::Maman(const char* uneChaine)
    {
        // do something
    }
     
    template<int N>
    Maman<N>::~Maman()
    {}
     
    template<int N>
    int Maman<N>::GetValue()
    {
        return N;
    }
    ne fonctionnera que si tout ce code se trouve dans Maman.hpp
    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

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Février 2009
    Messages
    17
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 17
    Par défaut
    Wow... je te remercie pour cette explication si détaillée, et tellement claire.

    Pour donner des détails sur ce que j'avais fait:
    oui, effectivement, l'implémentation des méthodes de Maman<int> étaient dans Maman.cpp, tandis qu'il existait Maman.hpp.

    Pire encore (allez, je m'autoflagelle...): mon essai sur cible x86 avec compilo MS Visual était fait avec les définitions + implémentations de Maman et de Fille, ainsi que le main, dans un seul et même fichier source. Donc je n'étais pas dans les mêmes conditions entre mon essai sur compilateur IAR pour ARM et mon compilateur Visual (j'étais à 10000 lieux de faire cette analyse).

    Peut-on en conclure que, en C++, toute classe templatisée doit être définie et implémentée dans un seul et même fichier? (ou voyez vous des cas où ça puisse ne pas être nécessaire?)


    Je profite de l'occasion pour vous faire part d'une observation sur ce que j'ai évoqué précdemment:
    lors de mes tests sur Visual (donc tout dans le même fichier... le problème précédemment évoqué n'est par conséquent pas rencontré), j'ai fait l'essai d'implémenter le constructeur de Fille de 2 façons différentes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Fille::Fille(const char* uneChaine):
        Maman<6>(uneChaine)
    {}
    (
    mais aussi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Fille::Fille(const char* uneChaine):
        Maman(uneChaine)
    {}
    (
    selon vous, que demande la norme: indiquer "Maman<6>" ou "Maman" dans la liste d'initialisation de Fille? (bref: on redonne la valeur du paramètre de template?)

    Encore merci pour votre aide!

    Nicolas

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par nicolo011 Voir le message
    Peut-on en conclure que, en C++, toute classe templatisée doit être définie et implémentée dans un seul et même fichier? (ou voyez vous des cas où ça puisse ne pas être nécessaire?)
    Presque, mais ce serait réducteur.

    Il serait en réalité plus juste de dire que la définition de toute classe template et l'implémentation de ses fonctions doivent être réunies dans chaque fichier qui les spécialise.

    Tu pourrais, en effet, avoir un fichier Maman.hpp proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifndef MAMAN_HPP
    #define MAMAN_HPP
    template<int N>
    class Maman
    {
    public:
        Maman(const char* uneChaine);
        virtual ~Maman();
        virtual int GetValue();
    };
     
    #endif
    et un fichier maman.tcc (tu remarquera que ce n'est pas un *.cpp, qui est classiquement l'extension utilisée pour les fichiers d'implémentation "classiques") qui prendrait la forme de

    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<int N>
    Maman<N>::Maman(const char* uneChaine)
    {
        // do something
    }
     
    template<int N>
    Maman<N>::~Maman()
    {}
     
    template<int N>
    int Maman<N>::GetValue()
    {
        return N;
    }
    Seul le fichier Maman.hpp est nécessaire pour l'héritage, mais le fichier Maman.tcc devrait être inclus au plus tard dans le fichier d'implémentation de la classe dérivée.

    Nous aurions donc un fichier Fille.hpp qui ressemblerait à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #ifndef FILLE_HPP
    #define FILLE_HPP
    #include <Maman.hpp>
     
    class Fille : public Maman<6>{
        /* ... */
    };
    #endif
    et un fichier Fille.cpp qui ressemblerait à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    #include <Fille.hpp>
    #include <Maman.tcc>
    /* implémentation des fonctions membres de fille */
    Cela fonctionnerait aussi

    Cela prend parfois sens de travailler de la sorte dans des situations bien particulières, mais bon, ce n'est malgré tout pas forcément des situations particulièrement courentes
    Je profite de l'occasion pour vous faire part d'une observation sur ce que j'ai évoqué précdemment:
    lors de mes tests sur Visual (donc tout dans le même fichier... le problème précédemment évoqué n'est par conséquent pas rencontré), j'ai fait l'essai d'implémenter le constructeur de Fille de 2 façons différentes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Fille::Fille(const char* uneChaine):
        Maman<6>(uneChaine)
    {}
    (
    mais aussi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Fille::Fille(const char* uneChaine):
        Maman(uneChaine)
    {}
    (
    selon vous, que demande la norme: indiquer "Maman<6>" ou "Maman" dans la liste d'initialisation de Fille? (bref: on redonne la valeur du paramètre de template?)
    Oui, normalement, tu dois préciser le type (ici la valeur) de spécialisation de la classe template.

    Ce devrait donc être
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Fille::Fille(const char* uneChaine):
        Maman<6>(uneChaine)
    {}
    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
    Membre averti
    Profil pro
    Inscrit en
    Février 2009
    Messages
    17
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 17
    Par défaut
    Merci d'avoir pris le temps pour ces explications.

    Citation Envoyé par koala01 Voir le message

    et un fichier maman.tcc (tu remarquera que ce n'est pas un *.cpp, qui est classiquement l'extension utilisée pour les fichiers d'implémentation "classiques")
    Par curiosité, l'extension *.tcc est-elle fixée arbitrairement par toi (parce que ce sont les 3 lettres présentes sur ta plaque d'immatriculation par exemple) ou est-ce un usage lorsqu'on veut inclure des fichiers avec implémentations de méthodes de leur donner cette extension? (bien entendu, je sais que rien n'est imposé au niveau des extensions, c'est pour cela que je parle d'"usage")

  9. #9
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par nicolo011 Voir le message
    Par curiosité, l'extension *.tcc est-elle fixée arbitrairement par toi
    Elle est à la fois fixée plus ou moins arbitrairement par moi et régulièrement utilisée.

    Citation Envoyé par nicolo011 Voir le message
    (parce que ce sont les 3 lettres présentes sur ta plaque d'immatriculation par exemple)
    Non, c'est MBU
    ou est-ce un usage lorsqu'on veut inclure des fichiers avec implémentations de méthodes de leur donner cette extension? (bien entendu, je sais que rien n'est imposé au niveau des extensions, c'est pour cela que je parle d'"usage")
    Disons que c'est un usage "arbitrairement fixé par moi"

    Plus sérieusement, l'usage est de ne pas utiliser les extensions existantes (donc ni la série h / hpp, ni la série c / cc /cpp ni aucune autre extension habituelle pour un type de fichier précis).

    L'extension tpp est régulièrement rencontrée, mais cela s’arrête là : j'ai déjà rencontré du impl, du inl et autres
    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

  10. #10
    Expert confirmé

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Plus sérieusement, l'usage est de ne pas utiliser les extensions existantes (donc ni la série h / hpp, ni la série c / cc /cpp ni aucune autre extension habituelle pour un type de fichier précis).
    Enfin, ta description des séries habituelles manque de choses qu'on voit. La meilleure explication que je connaisse est celle-ci (mais c'est en anglais).

    En passant, les .tpp et .ipp sont au choix inclus ou il faut, ou directement dans le .hpp.

Discussions similaires

  1. Double héritage de classe template, quelques questions
    Par the_angel dans le forum Langage
    Réponses: 2
    Dernier message: 29/07/2012, 12h26
  2. Réponses: 6
    Dernier message: 08/01/2012, 09h05
  3. Héritage de classes templates (CGAL)
    Par gilouu dans le forum C++
    Réponses: 5
    Dernier message: 08/10/2009, 11h30
  4. Héritage de classe template
    Par FunkyTech dans le forum Langage
    Réponses: 2
    Dernier message: 06/02/2008, 19h07
  5. Foncteur, classes templates et héritage
    Par Floréal dans le forum C++
    Réponses: 8
    Dernier message: 17/06/2007, 21h56

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