Salut,
Déjà, les opérateur +, -, * et /, s'il sont définis devraient renvoyer l'objet par valeur et non par référence...
En effet, l'idée générale est que, lorsque tu écris t1*t2, tu obtiens un nouvel objet, qui est différent de t1 et de t2
Par contre, la version permettant l'assignation avec ces opérateurs ( +=, -=,*= et /=) peut effectivement renvoyer une référence car il s'agit de renvoyer l'objet d'origine qui aura été modifié
Le fait que cet opérateur renvoie un objet par référence est le premier point qui pose problème:
l'objet qui contient le résultat (f dans le code) est créé statiquement dans la fonction (on dit "créé sur la pile"), et, comme tous les objets créés de cette manière, il est détruit lorsque l'on quitte la portée dans laquelle il est déclaré (autrement dit, ici, quand on quitte la fonction dans laquelle il est créé)...
Au final, ta fonction renvoie donc une référence sur un objet... qui n'existe plus (et pour lequel le destructeur a été implicitement appelé lorsque l'on a atteint l'accolade fermante de la fonction).
Toute tentative d'accéder au contenu de cette référence ne peut donc se traduire que par un comportement indéterminé (qui est le plus souvent synonyme de plantage de l'application)
Ce problème mis à part, je constate que
- tu utilise le constructeur par défaut lors de la déclaration de f... cela implique que f.m_taille est initialisé à la valeur par défaut (8)
- tu vérifie la taille de l'opérande de gauche et celle de l'opérande de droite, mais pas celle de l'objet qui devra contenir le résultat
Dans l'exemple que tu présente, tu as une chance: les tailles respectives de r (2) et de g (3) sont inférieures à la taille de f (8 par défaut), mais...
Si, un jour, tu venais à déclarer l'équivalent de r et de g comme ayant tous les deux des tailles supérieures à celle de f, tu te trouverais dans une situation scabreuse source d'erreur de segmentation parce que tu essayerais de faire rentrer, par exemple, 9 ou 10 éléments (ou plus) dans quelque chose qui ne peut en contenir... que 8 (ou moins)
Il faudrait donc intégrer dans ta logique de veiller à ce que l'objet de résultat dispose au minimum du nombre de cases suffisante pour pouvoir représenter l'ensemble des éléments de l'opérande le plus grand
Ceci dit, il y a dans ton code quelques problèmes qui justifient des conseils particuliers...
Les opérateurs < et > signifient respectivement "est plus petit que" et "est plus grand que"...
En toute logique, leur valeur de retour est donc un booléen (vrai ou faux),et, si l'on peut envisager le fait qu'ils prennent un entier comme argument, on s'attend généralement à ce qu'ils permettent de comparer deux objets de types identiques...
Pour ton opérateur d'affectation, il est généralement conseillé d'utiliser l'idiome dit du "copy and swap" (la copie et l'échange des données), de manière à s'assurer que les différentes ressources seront correctement libérées en temps utiles.
Après tout, tu dispose d'un constructeur par copie et d'un destructeur qui font parfaitement le boulot que l'on attend d'eux... il est donc possible d'utiliser le fait qu'une variable locale déclarée sur la pile est détruite lorsque l'on quitte la portée dans laquelle elle est déclarée à notre avantage
Ainsi, ton opérateur d'affectation pourrait prendre une forme proche de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| registre ®istre::operator=(const registre& r)
{
/* utilisons le constructeur par copie pour avoir un objet local
* identique à l'argument passé
*/
registre temp(r);
/* échangeons les membre de temp et de l'objet en cours */
std::swap(m_tab,temp.m_tab);
std::swap(m_taille, temp.m_taille);
/* nous aurions pu le faire manuellement sous une forme proche de
* m_tab^temp.m_tab;
* temp.m_tab^m_tab;
* m_tab^temp_m_tab;
* m_taille^temp.m_taille;
* temp.m_taille^m_taille;
* m_taille^temp.m_taille;
* mais comme le standard nous permet d'échanger des membres
* en une seule ligne, pourquoi s'en priver ???
*/
/* renvoyons notre objet courent */
return *this;
} // et laissons le destructeur libérer correctement les ressources de
// l'objet temporaire
} |
Et ceci m'amène tout naturellement à une dernière remarque (mais au combien importante)...
Tu l'aura remarqué sans que je ne le dise, l'utilisation de pointeurs et de l'allocation dynamique de la mémoire sont, très clairement, source de problèmes sans noms...
D'abord, parce qu'elles t'obligent à redéfinir une série de fonctions (le constructeur par copie, l'opérateur d'affectation et le destructeurs, pour ne pas les nommer) pour lesquelles tu aurais très bien pu faire confiance au compilateur pour les implémenter, si tu n'avait pas utilisé l'allocation dynamique...
Ensuite, parce que l'allocation dynamique de la mémoire revient à décider de prendre toi-même la responsabilité de déterminer quand l'objet doit être détruit et qu'il est donc très facile, si tu n'es pas attentif, d'en arriver à des situations dans lesquelles tu occasionnera soit des fuites mémoire (memory leaks), soit une double libération de la mémoire (les deux cas étant tout aussi catastrophiques l'un que l'autre).
Or, il se fait que le C++, bien que descendant tout droit du C, fournit une série de classes permettant de manipuler les collections "classiques" d'objets (tableaux contigus, piles, files, listes, arbres binaires et bien d'autres) de manière tout à fait transparente et bien plus sécurisante pour l'utilisateur que tout ce qu'il pourrait vouloir faire par lui-même.
Le conseil que l'on martèle donc à longueur de temps est de préférer en toute circonstances les solutions propres C++ à toute solution issue du C équivalente.
Dans ton cas, la solution propre au C++ que je te conseillerais d'utiliser est la classe vector, disponible dans l'espace de noms std par simple inclusion du fichier d'en-tête <vector>.
Cette classe présente en effet tous les avantages d'un tableau d'éléments dynamique, tout en rajoutant les faits
- qu'elle connait le nombre d'éléments qu'elle contient
- qu'elle ne nécessite aucune allocation dynamique de la part de son utilisateur
- qu'elle respecte le RAII (Ressource Acquisition Is Initialisation)
- qu'elle fournit même en cas de besoin une fonction membre permettant de te prévenir (en lançant une exception) si, d'aventure, tu venais à tenter d'accéder à un indice hors limite (au 9 eme élément d'un tableau qui n'en contient que 8, par exemple)
Le fait d'utiliser cette classe en remplacement de tes membres m_tab et m_taille te permettrait, entre autre:
- de faire confiance au constructeur par copie, à l'opérateur d'assignation et au destructeur implémenté par défaut (comprend: si tu ne les déclare pas explicitement) par le compilateur
- de gérer plus facilement n'importe quel nombre d'objet (ajout avec push_front ou push_back, insertion avec insert, suppression avec erase, vidange avec clear, ...)
- de pouvoir connaitre en permanence le nombre d'éléments constenus(avec size)
- d'accéder aux différents aux différents éléments avec les crochets "[]" (sans vérification d'index)
- d'accéder aux différents éléments en assurant une vérification de la validité de l'index (avec at)
- de faire tout cela de manière tout à fait "transparente" pour toi
- j'en passe, et de meilleures
Partager