Problème de surcharge d'opérateur <<
Résolu
Bonjour à tous,
J'ai créé une classe template
voici le fichier .h
Ensuite pour la testée dans le main.cpp
Mais au niveau de ligne
.... ça provoque l'erreur suivante :
... et j'ai ce warning aussi :
Merci pour votre aide
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
-
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 pastemplate
, Le compilateur voit une fonction qui déclare l'operateur<<
avec un paramètreStack_gene<T>
non pas avec unT template
, mais unT
précis (int
dans ton cas).
Tu définis la fonctiontemplate
(qui marche donc pour tout typeT
), mais tu as explicitement indiqué qu'une fonction nontemplate
était quelque part, et comme une nontemplate
et toujours préférée à unetemplate
, à 'l'édition des liens la nontemplate
deStack_gene<int>
est nulle part.
Pour corriger, tu peux indiquer que c'est latemplate
qui est amie.template<class U> friend std::ostream& operator<<(std::ostream&, Stack_gene<U>const&);
Attention, on ne devrait jamais utilisernew/delete
à moins d'en avoir la totale maîtrise. Par exemple essaie d'ajouter à ton main la ligneStack_gene<int> tab2 = tab1;
, ça va planter! En utilisant unstd::vector<T>
, c'est plus simple et plus sûr que ton allocation dynamique.
Tu ne sembles pas connaître l'utilisation deconst
ou tu l'as oublié, il manque de nombreuxconst
dans ton code. Par exemplecout << Stack_gene<int>{9};ne compilera pas. -
Merci pour ta réponse rapide Dalfab,
Pour ce qui est de rajoutertemplate<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 erreurD:\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 lenew
et ledelete
, c'est un petit exemple seulement, mais oui tu as raison,vector
est plus approprié, pour lesconst
. 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érateuroperator <<
qui me bloque actuellement.
Merci tes conseils. -
Note que j'ai utilisé
U
plutôt queT
dans mon exemple (j'ai juste ajouté l'indispensableconst
.)T
est un paramètre dutemplate
, créer unfriend template
qui le réutilise est confusionnant.
Oublierconst
ne simplifie pas, cela provoque des bugs dans ton code. Le motconst
n'est pas un mot de décoration.
Quant ànew
etdelete
, 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. -
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 -
Vous n’avez pas trouvé la réponse que vous recherchez ?
Posez votre question -
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; } -
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 classetemplate
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éclareritem
enconst
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. -
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. -
Ok merci beaucoup pour les éclaircissements.
je n'utiliserai plus de namespace dans mes sources,
Merci encore.
Cordialement -
Quelques petits éléments même si DalFab a déjà largement répondu (et très bien) à tes questions.
Lesnamespace
s
On n'utilise pas deusing namespace ...;
dans un header car ils vont contaminer tous les headers / sources qui vont l'inclure. Pour rappel, lesnamespace
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 classevector
: si elle est dans sonnamespace
(éventuellement lenamespace
"vide" = lenamespace
global) alors :-
vector
désigne::vector
(donc ta classe) ; -
std::vector
désigne la classe de la STL.
Si par contre tu as unusing namespace std
, le compilateur ne peut plus savoir sivector
désigne::vector
oustd::vector
. Et voilà pourquoi unusing 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, utiliserusing 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 (éventuellementfriend
).
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 qualificateurconst
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 écrireint 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 pourpx3
oupx3
. 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 qualificateurconst
aux paramètres manipulés en lecture seule.
Bonne chance -