excpetion dans le constructeur

excpetion dans le constructeur - C++ - Programmation

Marsh Posté le 27-07-2009 à 10:03:19    

Hi,
 
Comment gérer proprement le cas, ou je détecte disons au milieu d'un constructeur, une erreur qui fait que je ne souhaite pas continuer la création de l'objet. Je peux faire un throw? Quelle est la meilleur technique ?  
 
Merci.

Reply

Marsh Posté le 27-07-2009 à 10:03:19   

Reply

Marsh Posté le 27-07-2009 à 10:51:34    

faire une exception me semble tout à fait approprié, je ne crois même pas que tu aies vraiment d'autre solution.
Enfin, sur le projet sur lequel je bosse, on a désactivé les exceptions (pas dispo sur toutes les plateformes) donc on a une phase d'allocation (constructeur vide avec éventuellement des initialisations triviales) et une fonction de construction séparée, mais c'est pas trop la joie à utiliser
 
PS : tu n'aurais pas une légère dyslexie pour ton sujet ?
 
Edit : dans le chapitre 14.4.4 du Stroustrup, il précise que, pour une allocation standard, y'a pas de souci à lever une exception dans le constructeur, par contre, pour un new avec placement, c'est moins évident.


Message édité par theShOcKwAvE le 27-07-2009 à 10:56:49

---------------
last.fm
Reply

Marsh Posté le 27-07-2009 à 10:59:43    

merci, iou dsl pour le sujte j'ai tappé trop vtie

Reply

Marsh Posté le 27-07-2009 à 13:41:56    

Tu vas avoir un problème si tes membres construits n'ont pas de destructeurs. Par exemple:

Code :
  1. Foo() {
  2. this->p = new P;
  3. throw ...;
  4. }


la this->p ne sera pas détruite et sera perdu

Reply

Marsh Posté le 27-07-2009 à 13:52:32    

oui il faut que je fasse le boulot du destructeur moi même , et avant de lancer l'exception donc

Reply

Marsh Posté le 27-07-2009 à 14:17:51    

où alors tu te débrouilles pour ne travaillers qu'avec des smartpointers, des vectors, etc. C'est ça la vraie solution.

Reply

Marsh Posté le 27-07-2009 à 14:20:09    

dans ma boite ils sont obsédés par les perfs, l'expert en C++ de la boite, me déconseille même d'utiliser les exceptions,il préférerais dans le cas que j'ai exposé ici, un système de boolén, en gros une variable membre error , que je positionne à true si il y a eu un pbm dans le constructeur

Message cité 3 fois
Message édité par Glock 17Pro le 27-07-2009 à 14:20:21
Reply

Marsh Posté le 27-07-2009 à 14:21:43    

Glock 17Pro a écrit :

dans ma boite ils sont obsédés par les perfs, l'expert en C++ de la boite, me déconseille même d'utiliser les exceptions,il préférerais dans le cas que j'ai exposé ici, un système de boolén, en gros une variable membre error , que je positionne à true si il y a eu un pbm dans le constructeur


Pure folie.

Reply

Marsh Posté le 27-07-2009 à 14:27:51    

merci, ça me soulage, le mec a son statut d'expert, quand on est pas d'accord ça se termine,en euh je sais pas fait comme tu veux... wow ok d'accord

Reply

Marsh Posté le 27-07-2009 à 18:00:10    

Glock 17Pro a écrit :

merci, ça me soulage, le mec a son statut d'expert, quand on est pas d'accord ça se termine,en euh je sais pas fait comme tu veux... wow ok d'accord


c'ets pas un expert :/ c'est tout

Reply

Marsh Posté le 27-07-2009 à 18:00:10   

Reply

Marsh Posté le 04-08-2009 à 15:36:43    


Peut-être de vieux réflexes qui datent de l'époque où la gestion des exceptions n'était pas si fiable.
Je n'utilise pas non plus les exceptions dans les couches bas niveau d'un logiciel de calcul scientifique, surtout dans un constructeur, et on peut toujours s'en passer. Mais la technique de la variable membre 'error' citée plus haut est quand même peu élégante.
Je préfère les séquences du type :
...
objet = new Truc(parms1)
...
status = objet->Init(parms2)
if (status != NOERR)
{   delete objet;
    return status;
}
...
 
Séparer allocation et initialisation est une pratique tout à fait saine, même si celà parait compliquer un peu le code et demander un peu plus de rigueur.
Avis personnel: on devrait réserver l'usage des exceptions aux situations vraiment catastrophiques, genre échec d'allocation mémoire, pas pour gérer des paramètres non valides passés à un constructeur, ni non plus pour des erreurs mathématiques (divisions par 0, etc.), il s'agit là plutôt d'erreurs d'analyse.
Dans les couches de plus haut niveau, comme de l'interface utilisateur, je suis moins réticent à utiliser les exceptions, il n'y a pas trop de risque que ça dégrade les performances de façon visible.
 
A+
 

Reply

Marsh Posté le 04-08-2009 à 15:57:08    

Glock 17Pro a écrit :

dans ma boite ils sont obsédés par les perfs, l'expert en C++ de la boite, me déconseille même d'utiliser les exceptions,il préférerais dans le cas que j'ai exposé ici, un système de boolén, en gros une variable membre error , que je positionne à true si il y a eu un pbm dans le constructeur


 
Bah ça dépends.
Si il est anormal que la construction échoue (allocation, incohérence interne), exception.
Si ton objet a un état de validité interne variant, booléen interne. (J'en sais rien un objet qui maintiens je sais pas une connection à un truc hotplug par exemple, heu une webcam, un truc usb)


Message édité par bjone le 04-08-2009 à 15:58:36
Reply

Marsh Posté le 05-08-2009 à 13:06:46    

lambda0 a écrit :


Séparer allocation et initialisation est une pratique tout à fait saine

 

Non car ca viole une bonne tripotée de principe objet dont la RAII ...
Si t'as une chance que qqn oublie ton init, bah tu es sur que qqn l'oubliera.
Un objet doit, des sa construction, etre utilisable en plein avec un comportement défini.

 

A la limite, si tu veut pas lancer d'exception dans un ctor, fait un ctor exception-safe :o

Message cité 2 fois
Message édité par Joel F le 05-08-2009 à 13:11:07
Reply

Marsh Posté le 05-08-2009 à 13:44:32    

lambda0 a écrit :


Peut-être de vieux réflexes qui datent de l'époque où la gestion des exceptions n'était pas si fiable.
Je n'utilise pas non plus les exceptions dans les couches bas niveau d'un logiciel de calcul scientifique, surtout dans un constructeur, et on peut toujours s'en passer. Mais la technique de la variable membre 'error' citée plus haut est quand même peu élégante.
Je préfère les séquences du type :
...
objet = new Truc(parms1)
...
status = objet->Init(parms2)
if (status != NOERR)
{   delete objet;
    return status;
}
...
 
Séparer allocation et initialisation est une pratique tout à fait saine, même si celà parait compliquer un peu le code et demander un peu plus de rigueur.
Avis personnel: on devrait réserver l'usage des exceptions aux situations vraiment catastrophiques, genre échec d'allocation mémoire, pas pour gérer des paramètres non valides passés à un constructeur, ni non plus pour des erreurs mathématiques (divisions par 0, etc.), il s'agit là plutôt d'erreurs d'analyse.
Dans les couches de plus haut niveau, comme de l'interface utilisateur, je suis moins réticent à utiliser les exceptions, il n'y a pas trop de risque que ça dégrade les performances de façon visible.
 
A+
 


 
Elle est collector ta réponse, je vais poster ça sur thedailywtf :)

Reply

Marsh Posté le 05-08-2009 à 14:30:01    

Joel F a écrit :


Non car ca viole une bonne tripotée de principe objet dont la RAII ...
Si t'as une chance que qqn oublie ton init, bah tu es sur que qqn l'oubliera.
Un objet doit, des sa construction, etre utilisable en plein avec un comportement défini.
 
A la limite, si tu veut pas lancer d'exception dans un ctor, fait un ctor exception-safe :o


 
C'est pour ça que j'ai précisé que celà demandait un peu de rigueur, pour ne pas oublier les Init().
Et même dans ce cas, je ne laisse pas les champs à des valeurs aléatoires : valeur numériques initialisées à 0 par exemple, tous les pointeurs à NULL.
Je préfère aussi les objets complètement initialisés à la construction, mais ce n'est pas toujours possible, et quand ça l'est, ça peut être pénalisant du point de vue des performances.
Exemple 1 : construction d'un tableau d'objets, chaque objet devant être initialisé avec des paramètres différents et nécessitant des allocations mémoires.
Exemple 2 : recyclage des objets pour optimiser la gestion de la mémoires. Un objet qui n'est plus utilisé n'est pas détruit mais est marqué comme disponible et réutilisé plus tard par un nouvel appel d'initialisation.
Ca fait une différence quand on manipule des tableaux de plusieurs millions d'objets (des entités géométriques dans mon cas)
 
Techniques adaptées aux couches bas niveau d'un logiciel, où on fait attention aux performances.
 

Reply

Marsh Posté le 05-08-2009 à 15:51:09    

lambda0 a écrit :


C'est pour ça que j'ai précisé que celà demandait un peu de rigueur, pour ne pas oublier les Init().


Ta foi en l'etre humain est bien naive ...
 

lambda0 a écrit :


Et même dans ce cas, je ne laisse pas les champs à des valeurs aléatoires : valeur numériques initialisées à 0 par exemple, tous les pointeurs à NULL.
Je préfère aussi les objets complètement initialisés à la construction, mais ce n'est pas toujours possible, et quand ça l'est, ça peut être pénalisant du point de vue des performances.


Sauf que non
 

lambda0 a écrit :


Exemple 1 : construction d'un tableau d'objets, chaque objet devant être initialisé avec des paramètres différents et nécessitant des allocations mémoires.


Pattern factory + allocateur specifique pour std::container
 

lambda0 a écrit :


Exemple 2 : recyclage des objets pour optimiser la gestion de la mémoires. Un objet qui n'est plus utilisé n'est pas détruit mais est marqué comme disponible et réutilisé plus tard par un nouvel appel d'initialisation.


T'as le droit d'utiliser un appel direct au destructeur et un new de placement :o
 

lambda0 a écrit :


Techniques adaptées aux couches bas niveau d'un logiciel, où on fait attention aux performances.


Rien n'empeche de faire du bas niveau performant propre hein :o

Reply

Marsh Posté le 06-08-2009 à 22:02:51    

Glock 17Pro a écrit :

dans ma boite ils sont obsédés par les perfs, l'expert en C++ de la boite, me déconseille même d'utiliser les exception


Il me semble avoir lu un article ou plusieurs disant que les exceptions doivent êtres utilisées pour des erreurs "inattendues" et donc les exceptions sont rares et n'ont donc aucun effet sur les performances. L'article disait que si une exception était appelé régulièrement c'est qu'elle est utilisée à mauvais escient et qu'un code de retour doit être utilisé, bien sur c'est pas possible pour un new  :D, mais si ton exception est exceptionnelle du coup c'est pas grave.
En résumé les perfs on s'en fout pour les exceptions.  :o

 

Il faudrait que je retrouve cet article  :fou:

Message cité 1 fois
Message édité par sligor le 06-08-2009 à 23:28:40
Reply

Marsh Posté le 06-08-2009 à 22:43:09    

c'ets pas dans un GotW ?

Reply

Marsh Posté le 07-08-2009 à 09:24:08    

sligor a écrit :


Il me semble avoir lu un article ou plusieurs disant que les exceptions doivent êtres utilisées pour des erreurs "inattendues" et donc les exceptions sont rares et n'ont donc aucun effet sur les performances. L'article disait que si une exception était appelé régulièrement c'est qu'elle est utilisée à mauvais escient et qu'un code de retour doit être utilisé, bien sur c'est pas possible pour un new  :D, mais si ton exception est exceptionnelle du coup c'est pas grave.
En résumé les perfs on s'en fout pour les exceptions.  :o  
 
Il faudrait que je retrouve cet article  :fou:


 
moi je m'en sers également lorsque j'ai une fonction qui retourne un string par exemple , et qu'une erreur se produit, (ok je pourrais faire un argument output)

Reply

Marsh Posté le 10-08-2009 à 17:02:27    

lambda0 a écrit :


[...] C'est pour ça que j'ai précisé que celà demandait un peu de rigueur, pour ne pas oublier les Init(). [...)


en relisant cet excellent livre: http://www.amazon.fr/Standards-pro [...] 2744071447

 

je suis tombé sur un article qui dit que cette méthode est très mauvaise car on fini par oublier d'appeler l'initialisation.

 

La solution:

 
Code :
  1. class Foo {
  2. protected:
  3. Foo(...){}  /* le contructeur est inaccessible de l'extérieur */
  4. void Initialize(...) /* l'initialisation est inaccessible de l'extérieur*/
  5. public:
  6. static shared_ptr<Foo> Create(...){  /* on crée des instances uniquement avec create*/
  7.    shared_ptr<Foo> p( new Foo);
  8.   p->Initialize(...);
  9.   return p;
  10. }
 

Au final le code appelant ne peut pas faire de new ou Initialize séparément, on est obliger d'appler le méthode statique "Create":

Code :
  1. shared_ptr<Foo> instance = Foo:Create(....)
 

J'ai simplifié l'exemple du livre qui lui utilise les templates pour éviter de redéfinir "create" pour les classes dérivées.

 

Au passage ta fonction Create peux gérer les cas d'erreurs, liberer p et retourner un code d'erreur par référence. L'auteur précise que cette méthode doit être utilisée uniquement en cas de nécessité. Dans l'example de l'article, le problème était qu'on ne peut pas appeler de fonctions virtuelles pures dans un contructeur en C++.

Message cité 3 fois
Message édité par sligor le 10-08-2009 à 17:03:17
Reply

Marsh Posté le 10-08-2009 à 17:36:47    


exact je l'ai sur le bureau :o, faudrait peut être que je lise plus souvent :$

Reply

Marsh Posté le 10-08-2009 à 17:39:28    

c'est l'article 49 :)

Reply

Marsh Posté le 10-08-2009 à 17:48:10    

Joel F a écrit :

Non car ca viole une bonne tripotée de principe objet dont la RAII ...


RAII est un principe C++, pas objet [:masklinn:1]


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 10-08-2009 à 18:38:29    

masklinn a écrit :


RAII est un principe C++, pas objet [:masklinn:1]


pardon du raccourci.  
 
C'est pas ma faute si JAVA ne sait pas ce qu'est un destructeur :o

Reply

Marsh Posté le 10-08-2009 à 18:39:04    

sligor a écrit :


La solution:
...
 


Je trouve ça moi beau qu'un objet RAII-fier

Reply

Marsh Posté le 10-08-2009 à 18:41:55    

Joel F a écrit :

pardon du raccourci.

 

C'est pas ma faute si JAVA ne sait pas ce qu'est un destructeur :o


Oui enfin t'es bien gentil mais si pour toi "OO" ça donne le choix entre C++ et Java, on va pas aller bien loin [:masklinn:1]

 

Accessoirement Java a des destructeurs (finalizer), sauf qu'avec un GC non refcounting, ben l'appel du finalizer est pas déterministe, donc c'est pas d'un intérêt gigantesque [:masklinn:1]

 

Enfin, le destructeur n'est pas une notion générale de POO, c'est une notion spécifique à certaines implémentations de l'idée (et pas nécessairement les meilleures) [:masklinn:1]

Message cité 2 fois
Message édité par masklinn le 10-08-2009 à 18:45:24

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 10-08-2009 à 19:35:24    

masklinn a écrit :

 (et pas nécessairement les meilleures) [:masklinn:1]


non mais les moins pires alors  :ange:

Reply

Marsh Posté le 10-08-2009 à 20:02:59    

sligor a écrit :


en relisant cet excellent livre: http://www.amazon.fr/Standards-pro [...] 2744071447
 
je suis tombé sur un article qui dit que cette méthode est très mauvaise car on fini par oublier d'appeler l'initialisation.
 
La solution:
 

Code :
  1. class Foo {
  2. protected:
  3. Foo(...){}  /* le contructeur est inaccessible de l'extérieur */
  4. void Initialize(...) /* l'initialisation est inaccessible de l'extérieur*/
  5. public:
  6. static shared_ptr<Foo> Create(...){  /* on crée des instances uniquement avec create*/
  7.    shared_ptr<Foo> p( new Foo);
  8.   p->Initialize(...);
  9.   return p;
  10. }


 
Au final le code appelant ne peut pas faire de new ou Initialize séparément, on est obliger d'appler le méthode statique "Create":

Code :
  1. shared_ptr<Foo> instance = Foo:Create(....)



Tu te rends compte qu'au final Initialize de sert strictement à rien...

Reply

Marsh Posté le 10-08-2009 à 20:18:24    

Glock 17Pro a écrit :


non mais les moins pires alors  :ange:


Alan Kay disagrees [:masklinn:1]


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 10-08-2009 à 21:37:46    

masklinn a écrit :


Oui enfin t'es bien gentil mais si pour toi "OO" ça donne le choix entre C++ et Java, on va pas aller bien loin [:masklinn:1]  


 
Désolé de travailler avec des vrais trucs et pas des langages exotiques dont personne ne se sert :o
 

Spoiler :


Un troll se cache ici, sauras-tu le découvrir :o


Reply

Marsh Posté le 10-08-2009 à 21:48:39    

Taz a écrit :

Tu te rends compte qu'au final Initialize de sert strictement à rien...


lol

Reply

Marsh Posté le 10-08-2009 à 22:12:32    

Taz a écrit :

Tu te rends compte qu'au final Initialize de sert strictement à rien...


 [:biiij]

Reply

Marsh Posté le 11-08-2009 à 08:00:12    

Taz a écrit :

Tu vas avoir un problème si tes membres construits n'ont pas de destructeurs. Par exemple:

Code :
  1. Foo() {
  2. this->p = new P;
  3. throw ...;
  4. }


la this->p ne sera pas détruite et sera perdu


J'imagine que avant le throw, il y a surement un IF, dans ce cas si on fait un delete dans ce meme bloc juste avant le throw, ca pourrait marcher nan ?

Code :
  1. Foo() {
  2. this->p = new P;
  3. if ( yaUneCouille ) {
  4. delete this->p;
  5. throw ...;
  6. }
  7. }



---------------
Fresh
Reply

Marsh Posté le 11-08-2009 à 09:11:01    

Reply

Marsh Posté le 11-08-2009 à 09:18:47    


Bah probleme réglé alors :D
En fait j'ai du mal a voir les cas ou ca devrait etre plus compliqué que ca ( car apparemment ca fait débat :o )


---------------
Fresh
Reply

Marsh Posté le 11-08-2009 à 09:43:35    

bah cas ou une exception se propage depuis l'interieur d'une fonction appelée dans le constructeur et qui acquiere des ressources non triviales.
 
Reste ensuite le pb du constructeur incomplet etc...

Reply

Marsh Posté le 11-08-2009 à 09:50:44    

Joel F a écrit :


Reste ensuite le pb du constructeur incomplet etc...


 
c'est à dire ? même avec le if, on a un problème qui persiste ?

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed