|
Publicité ' | ||||||||||||||||||||||||
|
|
#1 |
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
Bonjour,
je propose un ajout dans la faq "Quelles sont les différences fondamentales entre le constructeur d'une classe et sa méthode Init() ?" Il est écrit qu'il existe 2 solutions. J'en vois une troisième: le scope guard. |
|
|
00
|
|
|
#2 |
|
Expert Confirmé
![]() Pierre Ingénieur développement logiciels Inscription : juin 2007 Messages : 1 205 ![]() |
Où donc veux-tu en venir?
__________________
Mes principes de bases du codeur qui veut pouvoir dormir:
|
|
00
|
|
|
#3 | |||
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
Il est écrit:
Citation:
Code :
|
|||
|
|
00
|
|
|
#4 |
|
Expert Confirmé
![]() Pierre Ingénieur développement logiciels Inscription : juin 2007 Messages : 1 205 ![]() |
Si tu ne peux pas tout acquérir dès le constructeur, c'est que tu veux construire une voiture sans avoir fabriqué ou acheté le moteur.
Ce que tu ne peux pas avoir automatiquement, demande le en argument, et tout devient simple. Si ce n'est pas encore le cas, raiie-le (*). Si c'est encore impossible, je pense que l'architecture est mal pensée. Je m'explique: Si j'ai besoin d'initialiser quelque chose que j'ai déjà construit, dans quel état était-il? Un objet est toujours dans un état connu et utilisable, il doit être construit ou ne pas être. * tiens, je l'aime bien, mon néologisme
__________________
Mes principes de bases du codeur qui veut pouvoir dormir:
|
|
10
|
|
|
#5 |
![]() ![]() ![]() Guillaume BelzBiochimiste Inscription : novembre 2008 Messages : 5 318 ![]() |
C'est pas une troisième méthode, c'est une implémentation particulière de init().
Qui peut rapidement poser des problèmes : si tu as plusieurs variables à initialiser, tu devras créer une variable bool pour chaque variable membre que tu veux initialiser, ce qui sera vite ingérable. La solution, c'est le RAII, c'est à dire initialiser dans le constructeur (et comme le dit leternel, si c'est pas possible, voir s'il n'y a pas un problème de conception). Et utiliser des pointeurs intelligents (voir pas de pointeur) pour les variables membres. Par contre, il faudra quand même mettre à jour cette faq (en fait, l'ensemble de la faq) pour le C++11 (auto_ptr...)
__________________
Vous souhaitez rejoindre l'équipe de bénévoles qui fait vivre Developpez (traduction, rédaction, modération) ? Contactez moi par MP. Ma page personnelle avec la liste de mes articles - Mon blog sur le C++, Qt et les GPU. Je suis régulièrement sur le chat pour les questions C++/Qt. Apprendre Qt 5 : vidéos d'installation (YouTube), extraites du livre Créer des applications avec Qt 5. |
|
00
|
|
|
#6 |
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
@leternel: J'ai du mal à comprendre ton message...
Selon toi, il serait interdit de construire un objet partiellement puis de le remplir ensuite par aggregation? L'idiome scope guard n'a donc pas lieu d'être? |
|
|
00
|
|
|
#7 |
![]() ![]() ![]() Guillaume BelzBiochimiste Inscription : novembre 2008 Messages : 5 318 ![]() |
Pas interdit, le C++ est un langage permissif...
Mais non respectueux du RAII, donc moins sur, moins robuste, moins lisible, etc L'approche C++ : le RAII Architecture Logicielle & Développement - RAII !
__________________
Vous souhaitez rejoindre l'équipe de bénévoles qui fait vivre Developpez (traduction, rédaction, modération) ? Contactez moi par MP. Ma page personnelle avec la liste de mes articles - Mon blog sur le C++, Qt et les GPU. Je suis régulièrement sur le chat pour les questions C++/Qt. Apprendre Qt 5 : vidéos d'installation (YouTube), extraites du livre Créer des applications avec Qt 5. |
|
00
|
|
|
#8 | |
![]() ![]() ![]() Guillaume BelzBiochimiste Inscription : novembre 2008 Messages : 5 318 ![]() |
Citation:
Après, effectivement, si on peut pas modifier un code existant pour mettre en place le RAII, que l'on peut pas utiliser des pointeurs intelligents (fournit pas le C++11, boost, Qt, ou même que l'on aurait créé), cela devient compliqué à faire du code robuste. Le code que tu présentes posera vite des problèmes à mon sens (peut être d'autres auront un avis différents). Pour les objets complexes, j'aurais tendance à préféré des DP composite, builder, etc. Ou un DP wrapper pour sécuriser/isoler un code non robuste (typiquement une lib C utilisée en C++), etc Pour revenir plus spécifiquement à la FAQ, il me semble que c'est pas une correction à faire (parce que c'est pas une troisième solution, mais une implémentation spécifique de init ; et qu'il faut bien faire des choix quand au contenu de la faq)
__________________
Vous souhaitez rejoindre l'équipe de bénévoles qui fait vivre Developpez (traduction, rédaction, modération) ? Contactez moi par MP. Ma page personnelle avec la liste de mes articles - Mon blog sur le C++, Qt et les GPU. Je suis régulièrement sur le chat pour les questions C++/Qt. Apprendre Qt 5 : vidéos d'installation (YouTube), extraites du livre Créer des applications avec Qt 5. |
|
|
00
|
|
|
#9 |
![]() ![]() |
Salut,
En fait, nous pourrions peut etre en effet parler du scope guard, tout en précisant clairement qu'il s'agit d'une manière de travailler qui devrait être réservée au cas "exceptionnel" dans lequel on n'a pas d'autre solution (comme la maintenance d'un code existant), mais qui est symptomatique d'un problème de conception à la base. Je suis le premier à prôner le fait qu'un objet se doit d'être directement dans un état cohérent dés la fin du constructeur, mais j'ai aussi rencontré suffisamment de code pour savoir qu'il est parfois difficile de faire en sorte que ce soit toujours le cas
__________________
en bas de page
|
|
|
20
|
|
|
#10 |
|
Expert Confirmé
![]() Pierre Ingénieur développement logiciels Inscription : juin 2007 Messages : 1 205 ![]() |
Personnellement, au boulot, je refuse systématiquement d'écrire du code qui n'est pas "constructor coherent".
Et chaque fois, c'est accepté, et je fais sauter un millier de lignes de code (oui, le projet est monstrueux…) D'ailleurs, en général, quand j'en parle tous les développeurs semblent d'accord pour reconnaître que le code est "mal écrit" dans ces portions. La seule "exception" est les "arguments nommés", que j'utilise pour la construction de requete sql, par exemple. Encore que, "construction en cours" peut-être vu comme un état valide d'un "RequestBuilder"
__________________
Mes principes de bases du codeur qui veut pouvoir dormir:
|
|
10
|
|
|
#11 | |
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
Il y a quelque chose qui m'échappe dans tout ça.
Déjà, dans ladite faq, je lis: Citation:
Vous n'êtes jamais tombé sur des problèmes où vous n'avez pas ce qu'il faut pour construire un objet complètement au moment où l'on a pourtant besoin de lui? gbdivers par exemple, je lis que tu es bio-informaticien. Tu dois bien avoir rencontré des problèmes de data-mining où tu dois construire une structure à partir de set de données partiellement complets non? Et encore ça s'est rien, le vrai problème c'est lorsqu'on doit construire un modèle à partir de ces données incomplètes et qu'on ne sait pas à l'avance de quelle façon on va construire cet objet. Quant à "supprimer des milliers de ligne de code", parfois c'est juste pas possible. Par exemple (et sans parler du cas - pourtant fréquent - du problème de temps), lorsque ces milliers de lignes de code effectuent un traitement tellement complexe qu'il nous faudrait des années pour seulement comprendre ce que fait ce code. |
|
|
|
00
|
|
|
#12 | |
![]() ![]() ![]() Guillaume BelzBiochimiste Inscription : novembre 2008 Messages : 5 318 ![]() |
A voir tes questions, je crois que l'on pas été assez précis et exacte sur les explications.
Citation:
Il n'est pas aberrant de créer un conteneur vide et de le remplir avec des push_back, on sait bien qu'il n'est pas possible dans la plupart des cas de créer le vecteur directement rempli. Ici on parle bien d'utiliser une fonction init en remplacement du constructeur, donc une fonction qui chargée de mettre la classe dans un étant cohérent. Ce qui implique que l'objet n'est pas dans un étant cohérent entre l'appel du constructeur et l'appel de init (si c'est pas le cas, alors c'est que le constructeur a fait son boulot et que la fonction init n'est pas en remplacement du constructeur, mais une vraie fonction d'initialisation) Il faut peut être reformuler certaines phrases...
__________________
Vous souhaitez rejoindre l'équipe de bénévoles qui fait vivre Developpez (traduction, rédaction, modération) ? Contactez moi par MP. Ma page personnelle avec la liste de mes articles - Mon blog sur le C++, Qt et les GPU. Je suis régulièrement sur le chat pour les questions C++/Qt. Apprendre Qt 5 : vidéos d'installation (YouTube), extraites du livre Créer des applications avec Qt 5. |
|
|
00
|
|
|
#13 | |
|
Expert Confirmé Sénior
![]() ![]() ![]() Inscription : août 2003 Messages : 4 522 ![]() |
auto_ptr<> est une capsule à la RAII. Aujourd'hui on dirait unique_ptr. C'est un scoped_guard. Un peu particulier certes, mais cela en est un. C'est ici un détail par rapport à comment écrire cette sorte de factory-method qui fait construction + post-init polymorphiqme.
Sinon, bonne idée que de déterrer ce point de FAQ car il n'adresse pas clairement ce qui est vraiment important. init() est un truc hérité de l'enseignement syntaxique par delta du C++ (j'invente les mots ; mais ils viennent d'une rumination pour une présentation que j'ai faite dans la boite pour inciter de migrer du C++ historique au C++ moderne). Au départ le C dont on fait vite le tour de la syntaxe. Le Pascal omniprésent dans l'éducation (chez nous du moins). Et des profs dans les années fin 80 à 90+ qui veulent présenter le C++. Donc il partent du C. Voilà une structure, on en fait un premier objet. Souvenez-vous les structures il faut les initialiser. Pouf une fonction init(). Ils continuent à présenter divers trucs et puis à un moment ils reviennent sur l'initialisation des données (notez le soucis au passage : la POO part d'une agrégation de données, et non de services rendus) et disent: ouais mais en fait il y a un truc fait pour initialiser -> les constructeurs. [notez l'approche itérative, d'où le "delta", qui ne s'intéresse qu'à présenter la syntaxe] Sauf que c'est déjà trop tard, les élèves ont eu deux semaines de TP entre temps à avoir utilisé des fonctions init() qui semblent remplir le besoin. Ils ont déjà pris la mauvaise habitude et sans même savoir pourquoi c'est une mauvaise habitude, parce que l'on ne leur a jamais parlé de cet invariant dynamique/"structurel" (pas le bon mot) En effet, la fonction init() oblige à gérer l'invariant "objet utilisable" de façon dynamique : avec un membre isReady/isInitialized, et un test de cette variable en début de chaque fonction. Tandis que l'approche constructeur nous assure un invariant statique : "par construction" (souvenez-vous des démonstrations en maths, c'est le même "par construction"), nous sommes assurés que si l'objet existe alors il est utilisable => pas de test à faire! Ce qui me fait penser à cette analyse d'une interview de Stroustrup, où il est cité à dire: http://www.theregister.co.uk/2012/12...n_c_plus_plus/ Citation:
Au final, je vois deux intérêts à init() : - la post-initialisation polymorphique, dont le point de FAQ parle - le code pré-exceptions -> je vous renvoie aux règles de codage chez google qui interdisent les exceptions (car base de code sans exceptions trop volumineuse pour changer ça) et qui demandent donc à utiliser des fonctions init(). Mais init() reste le cousin pauvre du constructeur. À bannir par défaut. [Ce peut valoir le coup d'insister sur cette histoire d'invariance dans le point de FAQ]
__________________
FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++ Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. |
|
|
|
10
|
|
|
#14 | |
![]() ![]() ![]() Guillaume BelzBiochimiste Inscription : novembre 2008 Messages : 5 318 ![]() |
Citation:
En pratique, c'est jamais fait comme ça (surcout, oubli), d'ou la nécessité de l'appel d'init par l'utilisateur (et la faq dans ce sens, avec la phrase "l'utilisateur doit l'appeler manuellement (risque d'oubli)" sur le risque d'erreur par l'utilisateur)
__________________
Vous souhaitez rejoindre l'équipe de bénévoles qui fait vivre Developpez (traduction, rédaction, modération) ? Contactez moi par MP. Ma page personnelle avec la liste de mes articles - Mon blog sur le C++, Qt et les GPU. Je suis régulièrement sur le chat pour les questions C++/Qt. Apprendre Qt 5 : vidéos d'installation (YouTube), extraites du livre Créer des applications avec Qt 5. |
|
|
00
|
|
|
#15 | |||
![]() ![]() |
Citation:
Mais la question se pose alors dans ces termes : Citation:
Citation:
Edit Cependant, on ne peut pas prétendre qu'un objet sur lequel init n'aurait pas été appelé soit "cohérent". La preuve en est que, si init n'a pas été appelée sur un objet, on ne peut absolument pas préjuger du résultat et qu'il faut donc s'assurer de la cohérence de l'objet au travers du test
__________________
en bas de page
|
|||
|
|
00
|
|
|
#16 | |||
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
Citation:
Je vais essayer de trouver une formulation avec laquelle nous soyons tous d'accord: l'init c'est comme les pointeurs, à n'utiliser que lorsqu'on ne peut pas faire autrement (formulation qui sous-entend que, parfois, on ne peut peut pas faire autrement). Après, Luc a raison sur le fait que derrière cette discussion, il y a un problème plus théorique concernant l'état d'un objet. Je suis bien incapable de formuler cela clairement, mais en gros, je pense que ce qui est important c'est l'état de l'objet (à un instant donné), et pas la façon dont l'objet est construit et/ou modifié. Donc l'invariance, mais peut-être pas uniquement (d'ailleurs, en parlant d'invariance, il serait peut-être bien de parler des assertions dans la faq?). Il y a également un problème de définitions: - Quand est-ce qu'un objet est "utilisable"? En effet, on peut imaginer un objet construit partiellement, avec une sémantique telle que si certaines partie manquent cela ait du sens (un exemple: sachant que telle donnée est manquante, cela permet de se situer dans l'arbre d'un traitement sur lequel on a pas le contrôle. D'ailleurs, de façon plus globale, l'absence de donnée a en fait parfois du sens.). - Qu'est-ce que l'invariance en c++? Je n'ai trouvé aucune définition qui me convienne. Une classe (un type en général) n'est pas un invariant en soi, mais elle définit des invariants. Mais comment appréhender ces invariants lorsqu'on a une classe qui évolue (par exemple une machine à état). - Qu'entendez-vous par "constructor coherent"? Enfin, un point qui n'est toujours pas clair pour moi: quid des constructeurs pas défaut? On est très souvent obligé de passer par un constructeur par défaut (stockage dans un conteneur template, basiquement). Est-ce que, dans ce cas, une fonction init() n'est pas envisageable? Un exemple de code pour illustrer ma question: Code :
|
|||
|
|
00
|
|
|
#17 | ||
|
Expert Confirmé
![]() Pierre Ingénieur développement logiciels Inscription : juin 2007 Messages : 1 205 ![]() |
Code :
Si tu fais un std::vector<char> v(5); ton vector contient cinq char, pleinement utilisables. un objet est utilisable si toute fonction membre est appelable et n'échoue pas hors interface. Par exemple, si tu as un std::fstream, il est forcément utilisable. Sinon, le constructeur aurait throwé, et tu n'aurais pas ton fstream.
__________________
Mes principes de bases du codeur qui veut pouvoir dormir:
|
||
|
00
|
|
|
#18 |
![]() ![]() Florian BlanchetEtudiant en Optique Inscription : août 2004 Messages : 1 060 ![]() |
- Invariant : N'importe quel propriété P que doit vérifier un objet de type T.
- propriété : N'importe quel assertion (vérifiable ? (*)) sur un objet. - ctor coherent : Dans le contexte, un constructeur qui, si aucune exception n'est lancée, va créer un objet respectant les invariants associés au type (sinon rien n'est crée). Au final l'intérêt pratique des fonctions init est assez faible je pense : - Pour le code interdit d'exception - Pour le besoin de ctor polymorphe comme l'a expliqué Luc, cependant je me demande si il existe autant de cas que ça où ce besoin est bien fondé et pas lié à une conception bancale. - En fonction à visibilité restreinte pour factoriser le code, inutile en C++11 (delegating ctor). Pour les exemples de BDD, data mining, etc, c'est quand même aux développeur de fixer les invariants de sa classe (tout en restant cohérant), il a aussi le pouvoir d'introduire des intermédiaires (aux invariants plus faible), au final je suis convaincu que l'on peut aboutir à des designs sur ces éléments sans avoir besoin d'init. (*) De manire "théorique" le vérifiable ne sert à rien, de manière pratique, le résultat d'une assertion non vérifiable ne doit pas avoir réellement d'impact.
__________________
"We can solve any problem by introducing an extra level of indirection" Butler Lampson "N'importe quel problème peut être résolu en introduisant un niveau d'indirection supplémentaire" Butler Lampson (traduction libre) |
|
|
00
|
|
|
#19 |
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
Je craint que cela ne suffise pas, du moins pas dans le domaine du développement logiciel. Prenons l'exemple d'un objet qui a deux états et une fonction doSomething(). Cette fonction fera des choses différentes selon l'état de l'objet; par conséquent, notre objet aura des propriétés différentes selon son état. Pourtant, au niveau du paradigme objet, le type MonObjet::doSomething() sera invariant (une fonction peut être considéré comme un type).
|
|
|
00
|
|
|
#20 | |
|
Expert Confirmé Sénior
![]() ![]() Inscription : août 2004 Messages : 3 669 ![]() |
Citation:
Si oui, étant donné que toute fonction peut potentiellement lever une exception, alors aucun objet n'est utilisable. |
|
|
|
00
|
Copyright © 2000-2013 - www.developpez.com