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
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
A voir également:
- Problème de surcharge d'opérateur <<
- 0644 quel opérateur ✓ - Forum Mobile
- 0668 quel opérateur ✓ - Forum Opérateurs & Réseaux mobiles
- 0758 quel opérateur - Forum Vos droits sur internet
- 0650 quel opérateur - Forum Opérateurs & Réseaux mobiles
- 0473 quel opérateur - Forum Loisirs / Divertissements
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
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
Tu définis la fonction
Pour corriger, tu peux indiquer que c'est la
Attention, on ne devrait jamais utiliser
Tu ne sembles pas connaître l'utilisation de
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
Tprécis (
intdans 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
templateet toujours préférée à une
template, à 'l'édition des liens la non
templatede
Stack_gene<int>est nulle part.
Pour corriger, tu peux indiquer que c'est la
templatequi 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
constou tu l'as oublié, il manque de nombreux
constdans ton code. Par exemple
cout << Stack_gene<int>{9};ne compilera pas.
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
Modifié le 2 juin 2022 à 15:34
Merci pour ta réponse rapide Dalfab,
Pour ce qui est de rajouter
comme tu me l'as conseillé, j'en ai pensé avant mais ça provoque une autre erreur
En ce qui concerne le
Merci tes conseils.
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
newet le
delete, c'est un petit exemple seulement, mais oui tu as raison,
vectorest 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.
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
27 mai 2022 à 15:29
Note que j'ai utilisé
Oublier
Quant à
Uplutôt que
Tdans mon exemple (j'ai juste ajouté l'indispensable
const.)
Test un paramètre du
template, créer un
friend templatequi le réutilise est confusionnant.
Oublier
constne simplifie pas, cela provoque des bugs dans ton code. Le mot
constn'est pas un mot de décoration.
Quant à
newet
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.
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
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
Merci d'avance pour ton aide
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
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
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; }
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
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
Par contre, j'ai juste une petite question :
En ce qui concerne // ne jamais utiliser :
En ce qui concerne :
... pourquoi déclarer
Merci pour le temps que tu consacre à m'aider.
Cordialement.
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
templatedans 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
.hseulement, 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
itemen
constpuisqu'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.
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
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.
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.
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
31 mai 2022 à 17:40
Ok merci beaucoup pour les éclaircissements.
je n'utiliserai plus de namespace dans mes sources,
Merci encore.
Cordialement
je n'utiliserai plus de namespace dans mes sources,
Merci encore.
Cordialement
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
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
On n'utilise pas de
Ainsi, rien ne t'empêche d'avoir ta classe
Si par contre tu as un
Exemple :
En soi, utiliser
L'opérateur
Concernant les templates : comme l'a dit DalFab, je recommande d'écrire l'opérateur
Exemple :
Le qualificateur
La position dans le type du mot clé
... de même que ces deux écritures sont équivalentes :
Par contre, les suivantes ne sont pas équivalentes :
Ici ces déclarations signifient respectivement:
Le principe est exactement le même avec les références (e.g.,
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
Bonne chance
Les
namespaces
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
namespaces 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
namespaceglobal) 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
vectordésigne
::vectorou
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é
constest 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 pour
px3ou
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
constaux paramètres manipulés en lecture seule.
Bonne chance