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

Résolu/Fermé
Mourad2009B Messages postés 108 Date d'inscription lundi 23 août 2010 Statut Membre Dernière intervention 28 octobre 2024 - Modifié le 2 juin 2022 à 15:32
mamiemando Messages postés 33372 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 22 novembre 2024 - 2 juin 2022 à 15:56
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

Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
25 mai 2022 à 20:58
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
Mourad2009B Messages postés 108 Date d'inscription lundi 23 août 2010 Statut Membre Dernière intervention 28 octobre 2024
Modifié le 2 juin 2022 à 15:34
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
Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
27 mai 2022 à 15:29
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
Mourad2009B Messages postés 108 Date d'inscription lundi 23 août 2010 Statut Membre Dernière intervention 28 octobre 2024
Modifié le 2 juin 2022 à 15:35
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

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
28 mai 2022 à 13:38
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
Mourad2009B Messages postés 108 Date d'inscription lundi 23 août 2010 Statut Membre Dernière intervention 28 octobre 2024
Modifié le 2 juin 2022 à 15:38
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
Dalfab Messages postés 706 Date d'inscription dimanche 7 février 2016 Statut Membre Dernière intervention 2 novembre 2023 101
28 mai 2022 à 19:22
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
Mourad2009B Messages postés 108 Date d'inscription lundi 23 août 2010 Statut Membre Dernière intervention 28 octobre 2024
31 mai 2022 à 17:40
Ok merci beaucoup pour les éclaircissements.
je n'utiliserai plus de namespace dans mes sources,
Merci encore.
Cordialement
0
mamiemando Messages postés 33372 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 22 novembre 2024 7 802
Modifié le 2 juin 2022 à 15:58
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