1050

./1047 : Justifié ou pas, c'est pas super malin d'avoir surchargé le code avec une gestion d'exception qui n'apporte strictement rien au problème dont il est question ici ^^ (et je ne parle pas des "sealed" et autres qui alourdissent la lecture, rendent le programme moins compréhensible pour quelqu'un qui ne parle le C#, et n'apportent rien eux non plus).

Sinon la solution qui me semble la meilleure est celle de Kevin également, à l'exception près que j'aurais laissé Square posséder les méthodes "getWidth" et "getHeight" au lieu de les remplacer par "getSideLength" (ça permet d'accéder en lecture à un carré comme à un rectangle sans se poser plus de questions) :
interface Rectangular { public int getHeight (); public int getWidth (); } class Rectangle implements Rectangular { private int h; private int w; public int getHeight () { return this->h; } public int getWidth () { return this->w; } public void setHeight (int h) { this->h = h; } public void setWidth (int w) { this->w = w; } } class Square implements Rectangular { private int l; public int getHeight () { return this->l; } public int getWidth () { return this->l; } public void setSideLength (int l) { this->l = l; } }

[edit 1] ah bah c'est quasiment la solution de Brunni en fait, j'avais mal lu, sauf que je préfère ne pas conserver deux variables distinctes dans Square, puisque ça introduit une incohérence possible qu'il est aussi simple d'éviter.

[edit 2] décidément j'ai lu n'importe comment, la solution de Kevin laisse getWidth et getHeight à la classe Square, donc je n'ai rien à reprocher ^^
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

1051

Zephyr (./1050) :
et je ne parle pas des "sealed" et autres qui alourdissent la lecture
Je ne peux pas te laisser dire ça, désolé. Le fait d'omettre le final (pour les Java speakers…) sur tes membres ou sur ta classe Square est une erreur de conception.
Ça veut bêtement dire qu'on peut créer un Carré qui ne soit en fait pas un carré. (Avec une classe dérivée adéquate bien sûr) Donc code incorrect…
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

1052

Sauf qu'on raisonne sur un problème borné par avance : on sait que Rectangle n'hérite de rien, et que rien n'héritera de Square. Bien sûr que dans une implémentation concrète tu vas ajouter tes "sealed", tes exceptions, tes super fonctions pour calculer le périmètre et tout le toutim. Mais dans le contexte bien particulier de ce topic, ça ne sert qu'à éloigner ton code de la version compréhensible la plus simplifiée possible amha.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

1053

(Hmm en C# les méthodes ne sont pas virtuelles par défaut non? Quel est l'intérêt du sealed? Pas vraiment équivalent au final de Java en tous cas)
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

1054

(Sisi, le sealed est exactement l'équivalent de final en java… Enfin je trouve que la signification du mot clé est plus claire d'ailleurs mais bon. Évidemment quand tu déclares une méthode abstract ou virtual, elle est forcément virtuelle hein cheeky)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

1055

Si j'ai bien compris, "sealed" sur une méthode, c'est généralement quand tu redéfinis une méthode d'une classe de base et décide que les classes descendantes n'auront plus le droit de la redéfinir.
Je ne l'ai jamais vu employé.

"sealed" sur une classe, c'est déjà plus courant.
avatar
Maintenant j'ai la flemme de garder une signature à jour sur ce site. Je n'ai même plus ma chaîne Exec sous la main.

1056

A ce moment là il est inutile si c'est comme le final non? (à part sur une classe) Puisque les méthodes et propriétés sont virtuelles par défaut confus
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

1057

Une méthode virtuelle tu peux la redéfinir dans l'implémentation d'une classe dérivée, alors que là tu peux pas si elle est sealed ?

1058

Oué mais si tu ne mets rien elle n'est pas virtuelle (exactement comme en C++). class A { public void Test() {} } class B : A { public void Test() {} // Warning: 'B.Test' hides inherited member 'A.Test'. Use the new keyword if hiding was intended. } A b = new B(); b.Test(); // Exécutera la méthode Test de A

En fait la différence est assez subtile: en fait sealed ne sert à rien dans une classe de base (il suffit de ne rien mettre), mais une méthode surchargée reste surchargeable (i.e. virtuelle) par défaut. class A { public virtual int X { get { return 1; } } } class B : A { public override int X { get { return 2; } } } class C : B { public override int X { get { return 3; } } }
Pour interdire C de surcharger encore le X de B il faut ajouter le mot clé sealed. Pfiou...
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

1059

Question sur laquelle je m'emmèle les pinceaux de manière récurrente...
J'ai une std::string dans un objet, elle est détruite comment cette conne ? Pourquoi serait-elle détruite à la destruction de mon objet ?
Dois-je alors avoir plutôt une std::string* sur laquelle je fais un delete dans le destructeur de mon objet ?

1060

is avec ça dans le header :
std::string *m_Caption;
Ca dans le constructeur :
m_Caption = new (std::nothrow) std::string (Caption);
(avec Caption : const char*)
Et ça dans le destructeur :
delete m_Caption;
?

1061

je ne comprends pas vraiment ton problème...
si tu as une std::string dans ton objet, elle sera détruite à la fin du destructeur. tu n'as pas besoin de passer par un std::string* et un new pour la créer et la détruire.
et si jamais tu ne pouvais pas l'initialiser avec Caption directement dans la liste d'init du constructeur de ta classe mais seulement plus tard, ce n'est pas grave. ta string sera vide, et tu n'auras qu'à lui affecter Caption plus tard avec l'opérateur =
avatar

1062

On te l'a déjà expliqué ce genre de trucs pourtant.
Une variable cesse d'exister en dehors de sa portée, auquel cas sa valeur est détruite (ça n'a de sens que pour les objets qui ont des valeurs plus complexes qu'un simple nombre donc).
Pour la portée d'une variable d'instance (champ), c'est la même chose, sauf qu'en plus la notion d'instance intervient. En gros, la portée de la variable prend fin avec l'instance de l'objet, donc sa valeur est détruite avec.
Donc oui, alloué avec un new, tu dois libérer (explicitement) avec un delete, mais, dans les autres cas, tu n'as rien à faire de spécial. (C'est le boulot du compilateur)
(cross)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

1063

aze (./1061) :
si tu as une std::string dans ton objet, elle sera détruite à la fin du constructeur.

Plutôt à la fin du destructeur cheeky

1064

cross...

Bon, en effet, vos explications m'éclairent. GC, je sais que j'ai déjà demandé, je ne sais pas pourquoi c'est toujours pas clair... Je n'arrive jamais à savoir si le compilateur est suffisamment intelligent pour ça (parce qu'il sait que mon objet crée une string, donc il devrait être capable de mettre en place tout seul le mécanisme de destruction de la string à la destruction de mon objet). Mais je sais jamais si ça se fait tout seul où si je dois lui dire... Apparemment, il sait faire à ce que tu dis.



Bon, dans la foulée, une autre question qui m'emmerde bien (je viens de percuter ça en fait, et j'ai pas du tout codé en conséquence, plein de trucs à revoir sorry) :

Un objet A construit lors de sa propre construction deux objets B et C en tant que membres privés.
L'objet B se construit normallement.
La construction de C échoue et il lance une exception.

Si A n'intercepte pas l'exception, mais qu'elle est récupérée par une "catch all" général à la base du programme (genre dans main()), alors B sera perdu dans la nature ?
Ca veut donc dire que c'est à A d'intercepter l'exception lancée par C pour nettoyer B avant de lancer sa propre exception ?

Si c'est ça (en fait je vois pas autre chose ^^), ça fait bien suer, je pensais justement qu'un catch all géant permettait d'éviter des blocs try/catch un peu partout dans les classes :/ Mon rêve s'écroule ? sad

1065

Pen^2> ahah, ouais ça pourrait fonctionner mieux comme ça cheeky

Folco> non, tous les objets déjà construits sont proprement détruits. donc le constructeur de B est appellé, mais ni le constructeur de A ou C car ils n'ont pas été entierement construits
et je n'arrive toujours pas à voir ce qui te pose problème avec ton exemple de string. quand ton objet est détruit, tous ses membres sont détruits, point. pour les pointeurs membres, il faut faire un delete sinon les objets pointés ne seront pas détruits.
avatar

1066

Folco (./1064) :
cross...
Bon, en effet, vos explications m'éclairent. GC, je sais que j'ai déjà demandé, je ne sais pas pourquoi c'est toujours pas clair... Je n'arrive jamais à savoir si le compilateur est suffisamment intelligent pour ça (parce qu'il sait que mon objet crée une string, donc il devrait être capable de mettre en place tout seul le mécanisme de destruction de la string à la destruction de mon objet). Mais je sais jamais si ça se fait tout seul où si je dois lui dire... Apparemment, il sait faire à ce que tu dis.
Ben c'est simple, t'as aucun moyen de forcer la libération d'un objet que tu n'as pas alloué avec new. (C'est interdit, et ça ferait très certainement planter ton programme) La conclusion logique et que c'est donc quelqu'un d'autre qui le fait à ta place… Non ? wink
Bon, dans la foulée, une autre question qui m'emmerde bien (je viens de percuter ça en fait, et j'ai pas du tout codé en conséquence, plein de trucs à revoir sorry) :

Un objet A construit lors de sa propre construction deux objets B et C en tant que membres privés.
L'objet B se construit normallement.
La construction de C échoue et il lance une exception.
Si A n'intercepte pas l'exception, mais qu'elle est récupérée par une "catch all" général à la base du programme (genre dans main()), alors B sera perdu dans la nature ?
Se tu l'as créé avec new, alors la probabilité est en effet de 100%. grin
Ca veut donc dire que c'est à A d'intercepter l'exception lancée par C pour nettoyer B avant de lancer sa propre exception ?
Ouep. Si l'exception fait partie du déroulement normal de ton programme (enfin qu'on soit clair, une exception ne doit jamais être normale, mais elle peut être prévisible et on peut pouvoir en récupérer, c'est de ça que je parle ici) alors oui, tu dois nettoyer proprement. Si c'est une exception pour dire que la pile est corrompue, tu peux laisser tomber et faire crasher le programme comme il faut tongue
Si c'est ça (en fait je vois pas autre chose ^^), ça fait bien suer, je pensais justement qu'un catch all géant permettait d'éviter des blocs try/catch un peu partout dans les classes :/
Si tu sais qu'une exception peut se produire et que tu sais que tu peux la gérer, alors tu dois la gérer oui. (Les exceptions potentielles devraient être documentées dans ton API pour plus de simplicité)
Mais un « grand catch all » ça doit juste te servir à afficher à l'utilisateur une description de pourquoi ton programme a crashé, avant de terminer l'exécution, jamais à autre chose.
Le reste, c'est de la gestion au cas par cas, et si tu n'est pas au courant que l'exception peut se produire, alors tu n'as pas à la gérer. (Ça sert à diagnostiquer les bugs aussi comme ça wink )
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

1067

GoldenCrystal (./1066) :
Mais un « grand catch all » ça doit juste te servir à afficher à l'utilisateur une description de pourquoi ton programme a crashé, avant de terminer l'exécution, jamais à autre chose.

"Click OK to terminate" #triclasse#

1068

Ah ouais, ça c'est du message d'erreur ! cheeky
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

1069

#triloveoui#

1070

grin

Et merci pour tout. Donc si je comprends bien, chaque objet en créant un autre doit intercepter les exceptions potentielles, c'est la règle.

Le grand catch all alors, c'est pourquoi ? Si tout est rattrapé à temps, pourquoi ne pas juste afficher un message et faire un return Error; ?

1071

Si j'ai bien compris ce que tu as dit Golden, et que j'ai ce code :
classe::truc()
{
  vector<objet_machin> vec;
  vec.push_back(machin1);
  vec.push_back(machin2);
}

vec sera libéré à la sortie de la méthode truc()
Par contre, les objets contenus dans vec le seront-ils ? cheeky fear cry

Sachant que si j'ai bien suivi ce que t'as dit, je n'ai pas moyen de détruire ces objets par moi-même, donc au compilateur de se démerder, non ? Il libère vec, hors il sait que vec contient des objets, donc il dit au destructeur de vec d'appeler le destructeur de tout ce qu'il contient ? Ou c'est pas ça du tout ? grin

1072

non, tu dois attraper les exceptions éventuelles uniquement si ça a un sens de faire ça.
dans ton exemple, si l'existence de A n'a pas de sens sans celle de C, alors pas la peine d'attraper l'exception lancée par C dans A. par contre tu peux mettre un try/catch autour de la création de A si ça a plus de sens. En gros tu attrapes l'exception quand tu as quelque chose à faire en cas d'erreur, sinon tu l'attrapes plus haut.

il y a d'autres cas où tu dois attraper les exceptions dans les constructeurs : si tu as alloué de la mémoire avec un new avant qu'il n'y ait l'exception, tu dois la libérer
//si C échoue, A doit échouer
class A
{
public:
  A()
  {
    b = new D();
    try
    {
	  c = new C();
	}
	catch(...)
	{
	  delete b;
	  throw; // relance l'exception attrapée par le catch
	}
  }
  B *b;
  C *c;
};

note que dans ce cas, il vaut mieux utiliser un std::auto_ptr ou équivalent pour contenir B* et C*. un auto_ptr va détruire automatiquement l'objet pointé quand le pointeur dest détruit, contrairement à un pointeur normal. et là, plus besoin de try/catch dans A

autre cas où tu peux vouloir attraper une exception : si tu veux traduire une exeption en une autre :
*mObj; };
[nosmile]
class MonAppli::A
{
public:
	A()
	{
		try
		{
			mObj = new une_lib_externe::objet();
		}
		catch(const une_lib_externe::exception &e)
		{
			throw MonAppli::Exception(e.des_details());
		}
	}
	une_lib_externe::objet 
([source] ne fonctionne pas avec le c++ ?)
comme ça si le reste de ton appli ne doit pas avoir connaissance de une_lib_externe, tu peux quand même faire remonter les exeptions et les traiter comme les autres

./1071> le vecteur détruit les objets qu'il contient (s'il contient un pointeur, il détruite le pointeur mais pas l'objet pointé)
mais il faut bien comprendre que le vecteur va contenir des copies des objets que tu as passé en paramètre à push_back. il y a toutes les chances que machin1, et machin2 aient été crées sur la pile. le vecteur ne peut pas s'approprier la portion de la pile dans laquelle sont machin1 et machin2 cheeky
donc il les copie
avatar

1073

Mes mes objets sur la pile seront de toute façon détruits à la sortie du scope, le vecteur ne détruisant que leur copie ? Donc au final, il ne reste rien.
aze (./1072) :
note que dans ce cas, il vaut mieux utiliser un std::auto_ptr ou équivalent pour contenir B* et C*. un auto_ptr va détruire automatiquement l'objet pointé quand le pointeur dest détruit, contrairement à un pointeur normal. et là, plus besoin de try/catch dans A

Mais attends c'est génial ça, c'est en fait un objet pointeur qui s'auto-détruit (ie avec ce sur quoi il pointe) quand on quitte sa portée ? Je regarde la doc de suite, ça a l'air génial boing

Et merci pour le topo sur les exceptions smile

Sinon, c'est [source = c] aussi pour le cpp

1074

ouais, c'est très utile. mais attention : l'auto_ptr ne peut pas être copié, car si on objet est pointé par deux auto_ptr ils sera détruit deux fois
avatar

1075

This class provides a limited garbage collection facility for pointers, by allowing pointers to have the elements they point to automatically destroyed when the auto_ptr object is itself destroyed.

auto_ptr objects have the peculiarity of taking ownership of the pointers assigned to them: An auto_ptr object that has ownership over one element is in charge of destroying the element it points to and to deallocate the memory allocated to it when itself is destroyed. The destructor does this by calling operator delete automatically.
Therefore, no two auto_ptr objects should own the same element, since both would try to destruct them at some point. When an assignment operation takes place between two auto_ptr objects, ownership is transferred, which means that the object losing ownership is reset to no longer point to the element (it is set to the null pointer).

( http://www.cplusplus.com/reference/std/memory/auto_ptr/ )
Si je comprends bien le §2 (mais j'ai du mal...), on peut créer un objet avec new, puis assigner le résultat à un auto_ptr après coup, il se chargera de faire le delete ? Le pied total embarrassed

cross -> oué vu pour la double destruction (§3), pas de copie, à moins qu'on réaffecte l'auto_ptr à un autre objet j'imagine.

Merci pour ce super tuyau top

1076

Ah non, je crois que j'ai mal compris, si on fait a = b (si ce sont des auto_ptr), alors b pointe vers null, et ne risque pas de faire une double destruction (the object losing ownership is reset to no longer point to the element (it is set to the null pointer)).

1077

ah ouais c'est bien ça, b contient null
je ne me souvenais plus du comportement des auto_ptr en cas de copie. en tous cas ça ne pouvait pas se comporter comme un pointeur normal
avatar

1078

Attention aussi en cas de passage à une fonction : il faut passer un auto_ptr en référence const, sinon si tu passes un auto_ptr par valeur, l'objet pointé est libéré lors de la sortie de la fonction wink

1079

je dirais même plus qu'il ne faut pas passer l'auto_ptr mais le pointeur brut avec auto_ptr::get() si possible
avatar

1080

oué ça fonctionne aussi cheeky