Problème de surcharge d'opérateur <<

Résolu
Mourad2009B Messages postés 145 Statut Membre -  
mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   -
Bonjour à tous,

J'ai créé une classe template

voici le fichier .h

#ifndef STACK_GENE_H
#define STACK_GENE_H
#include <iostream>

template<class T>
class Stack_gene
{
public:
      Stack_gene(int nmax);
      ~Stack_gene();
      Stack_gene& operator << (T n);
      Stack_gene& operator >> (T &);
      int operator ++ ();
      int operator -- ();
      friend std::ostream & operator << (std::ostream &flot, Stack_gene<T>  &tab);


private:
      int nmax;
      int nelmt;
      T *adv;
};

template<class T>  Stack_gene<T>::Stack_gene(int n)
{
      this->nmax = n;
      adv = new T [nmax];
      nelmt = 0;
}

template <class T> Stack_gene<T>::~Stack_gene()
{
      delete [] adv;
}

template <class T> Stack_gene<T>& Stack_gene<T>::operator << (T  n)
{
      if(nelmt < nmax)
            adv[nelmt++] = n;
      return (*this);
}

template <class T> Stack_gene<T>& Stack_gene<T>::operator >> (T  &n)
{
      if(nelmt > 0)
            n = adv[--nelmt];
      return (*this);
}

template <class T> int Stack_gene<T>::operator ++ ()
{
      //std::cout << "operator ++ nelmt = " << nelmt << std::endl;
      return (nelmt==nmax);
}

template <class T> int Stack_gene<T>::operator -- ()
{
      //std::cout << "operator -- nelmt = " << nelmt << std::endl;

      return (nelmt==0);
}

template <class T> std::ostream & operator << (std::ostream &flot, Stack_gene<T> &tab)
{
      flot << "// ";
      for(int i(0); i < tab.nelmt ; i++)
      {
            flot << tab[i] << "  ";
      }
      flot << " //";
      return flot;
}

#endif // STACK_GENE_H


Ensuite pour la testée dans le main.cpp

cout << "Hello World!" << endl;
      Stack_gene <int> tab1(9);
      cout << "tab1 pleine = " << ++tab1<< "\t tab1 vide = " << --tab1 << endl;
      tab1 <<  2 << 4 << 6 << 9 ;
      cout << "tab1 pleine = " << ++tab1<< "\t tab1 vide = " << --tab1 << endl;
      cout << "tab1 = " << tab1 << endl;


Mais au niveau de ligne
cout << "tab1 = " << tab1 << endl;


.... ça provoque l'erreur suivante :

D:\Fichiers_applications\C++\Projets_QtCreator\Test\Revis_gnrle\Exercices\Exercice_148\Exercice_148\main.cpp:13: erreur : undefined reference to `operator<<(std::ostream&, Stack_gene<int
debug/main.o: In function `main':
D:\Fichiers_applications\C++\Projets_QtCreator\Test\Revis_gnrle\Exercices\Exercice_148\build-Exercice_148-Desktop_Qt_5_15_2_MinGW_64_bit-Debug/../Exercice_148/main.cpp:13: undefined reference to `operator<<(std::ostream&, Stack_gene<int


... et j'ai ce warning aussi :

D:\Fichiers_applications\C++\Projets_QtCreator\Test\Revis_gnrle\Exercices\Exercice_148\Exercice_148\stack_gene.h:15: avertissement : friend declaration 'std::ostream& operator<<(std::ostream&, Stack_gene<T>&)' declares a non-template function [-Wnon-template-friend]
In file included from ..\Exercice_148\main.cpp:2:
..\Exercice_148\stack_gene.h:15:81: warning: friend declaration 'std::ostream& operator<<(std::ostream&, Stack_gene<T>&)' declares a non-template function [-Wnon-template-friend]
friend std::ostream & operator << (std::ostream &flot, Stack_gene<T> &tab);
^
..\Exercice_148\stack_gene.h:15:81: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)


Merci pour votre aide

9 réponses

  1. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
     
    Bonjour,

    Le warning t'indique en partie pourquoi tu as une erreur à l'édition des liens.
    Tu mets en ami une fonction qui n'est pas
    template
    , Le compilateur voit une fonction qui déclare l'operateur
    <<
    avec un paramètre
    Stack_gene<T>
    non pas avec un
    T template
    , mais un
    T
    précis (
    int
    dans ton cas).
    Tu définis la fonction
    template
    (qui marche donc pour tout type
    T
    ), mais tu as explicitement indiqué qu'une fonction non
    template
    était quelque part, et comme une non
    template
    et toujours préférée à une
    template
    , à 'l'édition des liens la non
    template
    de
    Stack_gene<int>
    est nulle part.
    Pour corriger, tu peux indiquer que c'est la
    template
    qui est amie.
       template<class U> friend std::ostream& operator<<(std::ostream&, Stack_gene<U>const&);

    Attention, on ne devrait jamais utiliser
    new/delete
    à moins d'en avoir la totale maîtrise. Par exemple essaie d'ajouter à ton main la ligne
    Stack_gene<int>  tab2 = tab1;
    , ça va planter! En utilisant un
    std::vector<T>
    , c'est plus simple et plus sûr que ton allocation dynamique.

    Tu ne sembles pas connaître l'utilisation de
    const
    ou tu l'as oublié, il manque de nombreux
    const
    dans ton code. Par exemple
    cout << Stack_gene<int>{9};
    ne compilera pas.
    0
  2. Mourad2009B Messages postés 145 Statut Membre 1
     
    Merci pour ta réponse rapide Dalfab,

    Pour ce qui est de rajouter
    template<class T> friend std::ostream & operator << (std::ostream &flot, Stack_gene<T>  &tab);

    comme tu me l'as conseillé, j'en ai pensé avant mais ça provoque une autre erreur
    D:\Fichiers_applications\C++\Projets_QtCreator\Test\Revis_gnrle\Exercices\Exercice_148\Exercice_148\stack_gene.h:15: erreur : Declaration of 'T' shadows template parameter


    En ce qui concerne le
    new 
    et le
    delete
    , c'est un petit exemple seulement, mais oui tu as raison,
    vector
    est plus approprié, pour les
    const 
    . C'est juste par esprit de simplification que je ne les ai pas mises. Ce qui est important c'est bien la surcharge de l'opérateur
    operator <<
    qui me bloque actuellement.

    Merci tes conseils.
    0
  3. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
     
    Note que j'ai utilisé
    U
    plutôt que
    T
    dans mon exemple (j'ai juste ajouté l'indispensable
    const
    .)
    T
    est un paramètre du
    template
    , créer un
    friend template
    qui le réutilise est confusionnant.
    Oublier
    const
    ne simplifie pas, cela provoque des bugs dans ton code. Le mot
    const
    n'est pas un mot de décoration.
    Quant à
    new
    et
    delete
    , le mieux est de les oublier quand on débute. Je ne fais du C++ que depuis 45 ans, je ne maitrise pas assez et je ne les ai plus utilisés depuis le C++11.
    0
  4. Mourad2009B Messages postés 145 Statut Membre 1
     
    Bonjour Dalfab,

    Merci pour tes conseils. Pour bien exposer le problème, j'essaye de perfectionner mes connaissances en C++, et je suis les cours et les exercices tirés d'un livre de Claude Delanoy. Après avoir essayé de résoudre l'exercice moi-même (bien sûr), j'ai regardé la solution, et c'est bien ce que j'ai fait, juste que ça bloque ici au niveau de la surcharge de l'opérateur
    <<
    , je joins au message les trois pages sous forme d'images concernant l'exercice en question.





    Merci d'avance pour ton aide
    0
  5. Vous n’avez pas trouvé la réponse que vous recherchez ?

    Posez votre question
  6. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
     
    En effet, la correction n'est pas bonne, le friend est incorrect. Voilà comment j'écrirais ce code:
    #ifndef STACK_GENE_H
    #define STACK_GENE_H
    #include <iostream>
    #include <vector>
    #include <stdexcept>
    
    // déclare l'opérateur<< (nécessaire pour le déclarer en ami plus loin)
    template<class T> class Stack_gene;
    template<class T> std::ostream&  operator<<(std::ostream&, Stack_gene<T>const&);
    
    template<class T>
    class Stack_gene
    {
    public:
       Stack_gene(std::size_t nmax) : adv(nmax) {}
       ~Stack_gene() = default;
       Stack_gene&  operator<<(T const& n) {
          if ( nelmt < adv.size() )
             adv[nelmt++] = n;
          else
             throw std::overflow_error{"débordement de Stack_gene"};
          return  *this;
       }
       Stack_gene& operator>>(T& n) {
          if ( nelmt > 0u )
             n = adv[--nelmt];
          else
             throw std::underflow_error{"dépilage d'un Stack_gene vide"};
          return  *this;
       }
       bool  operator++()const {  // mauvaise idée de sortir un opérateur de sa sémantique
          return  (nelmt==adv.size());
       }
       bool  operator--()const {
          return  (nelmt==0u);
       }
       // template<class U> friend std::ostream& operator<<(std::ostream&, Stack_gene<U>const&);
       // mais on peut plutôt considérer en ami que l'opérateur<< pour le T actuel
       // à condition d'avoir préalablement déclaré le template d'operateur<<
       friend std::ostream& ::operator<< <>(std::ostream&, Stack_gene const&);
    
    private:
       std::size_t  nelmt{};
       std::vector<T>  adv;
    };
    
    template<class T>  std::ostream& operator<<(std::ostream &flot, Stack_gene<T>const& tab) {
       flot << "// ";
       std::size_t  max = tab.nelmt;
       for ( auto const& item : tab.adv ) {
          if ( !max-- )
             break;
          flot << item << "  ";
       }
       flot << "//";
       return  flot;
    }
    
    #endif // STACK_GENE_H

    #include "stack_gene.h"
    // ne jamais utiliser : using namespace std;
    
    int main(){
       Stack_gene<int> tab1(9);
    
       std::cout << std::boolalpha << "tab1 pleine = " << ++tab1 << "\t tab1 vide = " << --tab1 << std::endl;
       tab1 << 2 << 4 << 6 << 9;
       std::cout << "tab1 pleine = " << ++tab1 << "\t tab1 vide = " << --tab1 << std::endl;
       std::cout << "tab1 = " << tab1 << std::endl;
       int  x;
       tab1 >> x;
       std::cout << "sortie de dernier élément : " << x << std::endl;
    }
    0
  7. Mourad2009B Messages postés 145 Statut Membre 1
     
    Merci Dalfab pour tes éclaircissements, que je trouve très enrichissant.

    Pour expliquer un peu la situation, j'ai fait du C++, et jusqu'à maintenant de manière basique, mais j'ai pris la décision d'approfondir mes connaissance dans ce langage que je trouve très puissant. C'est pour cette raison que j'ai commencé par le C++ standard et maintenant j'entame le C++ moderne (avec tout ce qui est les pointeurs intelligents, les auto, etc...) et qui simplifient grandement la vie.

    Pour ce qui est de ta méthode, je la prendrai pour modèle à chaque fois où j'ai une classe
    template
    dans mes programmes.

    Par contre, j'ai juste une petite question :
    En ce qui concerne // ne jamais utiliser :
    using namespace std;
    il est toujours dit de ne pas les utiliser dans les
    .h
    seulement, mais dans les
    .cpp
    , ils sont autorisés et c'est même conseillé de les mettre pour simplifier le code.

    En ce qui concerne :
    for ( auto const& item : tab.adv ) {
    if ( !max-- )
    break;
    flot << item << " ";
    }

    ... pourquoi déclarer
    item
    en
    const
    puisqu'il change de valeur durant la boucle, en plus il ne risque pas d'être modifié dans la boucle.

    Merci pour le temps que tu consacre à m'aider.
    Cordialement.
    0
  8. Dalfab Messages postés 638 Date d'inscription   Statut Membre Dernière intervention   102
     
    Le using namespace std est désastreux dans les entêtes mais est aussi un problème dans les sources, tu trouveras des discussions à ce sujet dans le forum. En utilisant cela tu économises quelques caractères mais tu ramènes dans l'espace global des milliers de noms et risque un bug sournois.

    Non item ne change pas de valeur, à chaque tour de boucle on a un nouvel item. Mettre const le plus souvent possible permet entre autre de détecter rapidement des incohérences, c'est un peu comme éviter le using namespace, ça prend quelque caractères de plus et ça permet d'éviter certains problèmes. On peut enlever le mot ici car tab.adv est const et il n'ajoute rien, item sera forcément const&, c'est finalement juste une info pour le lecteur.
    0
  9. Mourad2009B Messages postés 145 Statut Membre 1
     
    Ok merci beaucoup pour les éclaircissements.
    je n'utiliserai plus de namespace dans mes sources,
    Merci encore.
    Cordialement
    0
  10. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
     
    Quelques petits éléments même si DalFab a déjà largement répondu (et très bien) à tes questions.

    Les
    namespace
    s

    On n'utilise pas de
    using namespace ...;
    dans un header car ils vont contaminer tous les headers / sources qui vont l'inclure. Pour rappel, les
    namespace
    s ont été introduits pour éviter les collisions entre les noms de classes / structures / fonctions de différents projets.

    Ainsi, rien ne t'empêche d'avoir ta classe
    vector
    : si elle est dans son
    namespace
    (éventuellement le
    namespace
    "vide" = le
    namespace
    global) alors :
    • vector
      désigne
      ::vector
      (donc ta classe) ;
    • std::vector
      désigne la classe de la STL.


    Si par contre tu as un
    using namespace std
    , le compilateur ne peut plus savoir si
    vector
    désigne
    ::vector
    ou
    std::vector
    . Et voilà pourquoi un
    using namespace std;
    dans un header peut avoir un impact catastrophique. Par contre, rien ne t'interdit de l'utiliser dans une fonction, car cela ne "débordera" pas en dehors.

    Exemple :

    void f(){
        using namespace std;
        //...
    }


    En soi, utiliser
    using namespace std;
    dans un fichier source (
    .cpp
    ) ne me choque pas vraiment, car tu maîtrises tes includes et tes classes, donc a priori il n'y a pas de risque de collision impromptues. Par contre, c'est à proscrire dans tes headers car cela limite la réutilisabilité de ton code dans d'autres projets à cause des raisons qu'on vient d'évoquer.

    L'opérateur
    <<


    Concernant les templates : comme l'a dit DalFab, je recommande d'écrire l'opérateur
    <<
    sous forme d'opérateur externe et pas sous la forme d'une méthode (éventuellement
    friend
    ).

    Exemple :

    #include <ostream>
    #include <vector>
    
    template <typename T>
    std::ostream & operator << (std::ostream & out, const std::vector<T> & v) {
        out << '[':
        for (auto x : v) out << ' ' << x;
        out << " ]";
        return out;
    }


    Le qualificateur
    const


    La position dans le type du mot clé
    const
    est importante. En gros, il s'applique à tout ce qui est à gauche. S'il s'applique à un type qui n'est ni une adresse, ni une référence, il peut être écrit indifféremment à gauche ou à droite de ce type. Ainsi ces deux écritures sont équivalentes :

    const int x:
    int const x;


    ... de même que ces deux écritures sont équivalentes :

    const int * px;
    int const * px;


    Par contre, les suivantes ne sont pas équivalentes :

    const int * px1;
    int * const px2;
    const int * const px3;


    Ici ces déclarations signifient respectivement:
    • l'entier pointé par
      px1
      qui est maintenu constant (i.e.,
      *px1
      ) - mais pas l'adresse ;
    • l'adresse
      px2
      est maintenue constante, mais pas
      *px2
      ;
    • px3
      et
      *px3
      sont tout deux maintenus constants (on aurait aussi pu écrire
      int const * const px3
      ).


    Le principe est exactement le même avec les références (e.g.,
    const int & x
    ).

    En pratique, on a pas vraiment besoin de garantir la constance du pointeur, qui de toute façon est passé en recopie lors de l'appel de fonction. C'est pourquoi on voit rarement des déclarations comme pour
    px3
    ou
    px3
    . Par contre la constance de l'objet pointé a souvent beaucoup d'importance : si une fonction ne garantit pas la constance d'un paramètre, alors il n'est pas possible de lui passer une instance constante de ce paramètre. Cela restreint donc son champ d'utilisation, et c'est pourquoi, il est recommandé de mettre le qualificateur
    const
    aux paramètres manipulés en lecture seule.

    Bonne chance
    0