
Envoyé par
gizmo27
@koala01 : en fait je me suis un peu précipité pour demander pourquoi alors que je penses avoir la réponse, qui est : c'est parce que c'est le gars qui a programmé la fonction setParent() ainsi (après tout si je veux vraiment savoir pourquoi je vais voir la fonction au détail mais je ne suis pas sûr d'en avoir le courage).
Maintenant je suppose que si ça a été fait comme ça je suppose qu'il y a une ou plusieur(s) raison(s).
Ne connaissant pas les raisons je trouve "dommage" de rendre obligatoire la création d'une QLineEdit par allocation dynamique si on veut l'intégrer à un QDialog.
Après je suppose que je n'ai pas à ramener ma fraise car les gars qui ont fait Qt y connaissent bien plus que moi et qu'ils savent très bien ce qu'ils font.
C'est juste pour discuter voilà tout.
Bon week-end. Cordialement, Gizmo.
Tu as tout à fait le droit de te poser la question 
Et tu as tout à fait le droit d'obtenir une réponse qui te permettra de comprendre. La voici :
Pour commencer, il faut déjà savoir ce qui se cache derrière la fonction setParent():
Pour faire simple, la fonction setParent() d'un objet va invoquer la fonction addItem() du parent qui lui est transmis en argument.
En très gros, elle va prendre la forme de (*)
1 2 3 4 5 6 7 8 9 10 11
| void QWidget::setParent(QWidget * newParent){
/* s'il y a déjà un parent, on lui demande d'abandonner l'enfant */
if(parent_){
parent_->removeItem(this);
}
/* on indique au nouveau parent (s'il existe) qu'il a un enfant de plus */
if(newParent)
newParent->addItem(this);
/* et on défini le nouveau parent comme parent de l'objet */
parent_=newParent;
} |
(*)J'ai utilisé mes propres noms, il y a toute une mécanique mise en place par Qt qui n'est pas prise en compte, mais le résultat est quasiment identique 
Pour comprendre pourquoi c'est fait de la sorte, il faut te rappeler cinq choses :
1- Si tu veux pouvoir profiter du polymorphisme d'inclusion (c'est ce que l'on appelle, par abus de langage le polymorphisme
) et pouvoir gérer des QWidget -- quel que soit leur type réel -- comme s'ils étaient des QWidget, sans autre précision, tu dois les manipuler soit au travers de référence soit au travers de pointeurs.
Si tu essayes d'affecter une instance dont le type dérive de QWidget (par exemple, un objet de type QLineEdit) à un objet (qui n'est ni une référence, ni un pointeur) de type QWidget, tu auras un effet nommé "slicing": tu perdras toutes les informations qui font de QLineEdit un QWidget "particulier".
Et il ne faut pas oublier qu'un QLineEdit n'est jamais qu'un type de widget parmi plein d'autres! : Tous les objets visuels que tu peux placer dans une interface graphique héritent -- de manière directe ou indirecte -- de QWidget.
Si tu perds les informations qui font d'un QLineEdit un QWidget particulier, il en ira de même pour les QComboBox, les QLabel, les QPushButton et tous les autres 
Tu imagines sans peine les problèmes que cela pourrait occasionner 
2- Lorsque tu crées un objet sans avoir recours à la création dynamique (par exemple, sous la forme de QLineEdit myEdit;), sa durée de vie s'étend de la déclaration de la variable (ici myEdit) à l'accolade fermante de la portée dans laquelle elle est déclarée.
Ainsi, si tu écris une fonction proche de
1 2 3 4 5
|
void foo(){
QLineEdit myEdit;
/* ... */
} // myEdit est automatiquement détruit ici |
ton objet myEdit sera automatiquement détruit lorsqu'on atteindra l'accolade fermante sauf si myEdit est renvoyé par valeur par la fonction:
1 2 3 4 5
| QLineEdit foo(){
QLineEdit myEdit;
/* ... */
return myEdit; //myEdit continue à exister tant que la variable qui le récupère existe
} |
Mais je viens de t'expliquer que tu subirait un problème de slicing si tu décidais d'affecter ton QLineEdit à un objet de type QWidget.
Tu subirais donc le phénomène de slicing de plein fouet si tu modifiait foo pour qu'elle prenne la forme de
1 2 3 4 5
| QWidget foo(){
QLineEdit myEdit;
/* ... */
return myEdit; // Tu renvoie un QWidget, et tu perds donc tout ce qui fait de myEdit un QLineEdit
} |
De même, tu subirait le slicing de plein fouet avec un code proche de
1 2 3 4 5 6 7 8 9 10
|
QLineEdit foo(){
QLineEdit myEdit;
/* ... */
return myEdit; //myEdit continue à exister tant que la variable qui le récupère existe
}
void bar(){
QWidget obj =foo(); // Ce qui était un QLineEdit n'est maintenant plus qu'un QWidget
/* ... */
} |
Tu me diras sans doute que la solution serait de déclarer obj comme étant un QLineEdit.
Ce n'est pas faux, mais c'est sans compter sur le fait que foo() peut très bien renvoyer une fois un QLineEdit et qu'une surcharge de la fonction (ou une redéfinition de son comportement, si c'est une fonction membre d'une classe et qu'elle est virtuelle) pourrait vouloir renvoyer un QPushButton, un QLabel ou n'importe quoi d'autre.
Tu me diras alors sans doute "bah, renvoyons notre QLineEdit par référence"
Cela nous donnerait un code proche de
1 2 3 4 5 6 7 8 9
| QWidget & foo(){ // QLineEdit hérite de QWidget. Il peut donc "passer pour"
// un objet de type QWidget
QLineEdit myEdit;
/* ... */
return myEdit;
}
void bar(){
QWidgt & obj=foo();
} |
Mais tu aurais tout faux! car la durée de vie d'un objet ne s'étend au delà de l'accolade fermante de la portée dans laquelle il est déclaré que s'il est renvoyé par valeur, et non par référence.
Obj serait donc une référence qui fait ... référence à un objet qui a déjà été détruit. Toute tentative d'accès à une fonction membre de obj dans bar a de fortes chances de se terminer en erreur de segmentation (en tout cas, c'est un comportement indéfini).
3- La seule solution que l'on ait pour contourner le problème est d'accepter de prendre la responsabilité de la durée de vie de l'objet. Autrement dit, de recourir à l'allocation dynamique.
Ainsi, le code suivant fonctionnera sans problème
1 2 3 4 5 6 7 8 9 10 11 12 13
| QWidget * foo(){
QLineEdit * ptr = new QLineEdit; // on prend la responsabilité de la durée de vie
// de ptr
/* ... */
return ptr;
}
void bar(){
QWidget * ptr_to = foo();
/* on peut utiliser ptr_to ici, quel que soit le type réel de l'objet renvoyé par
* foo
*/
delete ptr_to; // mais il ne faut pas oublier de le détruire
} |
A ce sujet, il faut se rappeler qu'un pointeur n'est jamais qu'une valeur numérique "tout ce qu'il y a de plus normale" à ceci près qu'elle représente l'adresse mémoire à laquelle nous trouverons un objet du type indiqué.
foo renvoie donc par valeur une valeur numérique qui correspond à l'adresse mémoire à laquelle nous trouverons l'objet que nous avons créé 
4- On ne peut pas créer une collection de références.
On peut créer une collection d'objets sous une forme proche de
std::vector<MyClass> tab; // sous Qt ce sera sans doute plutôt QVector, mais ce n'est pas le problème
On peut créer une collection de pointeurs sur des objets -- car, après tout, un pointeur n'est qu'une valeur numérique -- sous une forme proche de
std::vector<MyClass *> tab;
Mais on ne peut pas créer une collection de référence sur des objets, parce qu'une référence n'est qu'un alias de (un autre nom pour) l'objet référencé. Le code suivant sera refusé par le compilateur
std::vector<MyClass & > tab; // refusé: impossible de créer une collection de références
5 - C++ ne fournit aucun mécanisme de garbage collector: quand le développeur décide de prendre la responsabilité de la durée de vie d'un objet en ayant recours à new, il prend explicitement la responsabilité de décider du meilleur moment pour détruire l'objet en question.
S'il n'y fait pas attention, il lui sera facile de "perdre" l'adresse mémoire à laquelle se trouve un objet dont il a accepté de prendre la responsabilité de la durée de vie, et il n'y aura aucun moyen de la récupérer par la suite. Cela occasionnera ce que l'on appelle une "fuite mémoire".
Si l'on perd trop souvent les adresses auxquelles se trouvent les objets dont on a la responsabilité de la durée de vie, c'est -- à terme -- la stabilité de tout le système qui sera compromise, car il arrivera un moment où l'ordinateur ne trouvera plus assez de mémoire pour faire son boulot 
Ca, c'est le décors dans lequel on travaille.
Maintenant, il faut comprendre que nous ne sommes pas en Chine : un enfant n'aura jamais qu'un parent à la fois, mais un parent peut avoir un nombre indéterminé d'enfants: il en aura peut être 3, peut être 5 ou peut être 150... Vas savoir.
Et surtout, chaque enfant peut être un QLineEdit, un QLabel, un QPushButton, un QMenu, un QComboBox ou ... en gros, n'importe quel type dérivé (de manière directe ou indirecte) de QWidget.
Et comme il serait virtuellement impossible (et très coûteux en mémoire) d'avoir une collection de QLineEdit, une autre de QLabel, une troisième de QPusButton, et ainsi de suite -- on risquerait toujours d'oublier de rajouter une collection pour un type particulier, ce qui serait dommage -- la meilleure solution est de se dire que tous les enfants sont des ... QWidget.
Et comme on ne peut pas créer de collection de références, qu'une collection d'objets provoquerait un phénomène de slicing, la seule solution qu'il nous reste, si l'on veut profiter du polymorphisme, c'est une collection de pointeurs.
Ce qui tombe bien, parce que le pointeur est, justement, très fortement relié à l'allocation dynamique de la mémoire et que l'allocation dynamique de la mémoire est le meilleur moyen d'être en mesure de créer un objet présentant des comportements polymorphes "quelque part" et d'être en mesure de le renvoyer pour qu'il soit utilisé "ailleurs".
Mais, quand on parle d'allocation dynamique de la mémoire, il ne faut pas oublier qu'elle va de pair avec la nécessité de décider du meilleur moment pour libérer cette mémoire!
Qt avait donc deux solutions :
Soit, de laisser le développeur libre de travailler de la manière qu'il voulait en le laissant totalement responsable de ses actes.
Le développeur aurait pu créer un objet en ayant recours à l'allocation dynamique de la mémoire... ou non et transmettre l'adresse correspondante au parent, soit en ayant recours à l'opérateur addressof, soit en transmettant le pointeur vers l'objet créé au travers de new.
Et il aurait été du ressort du développeur de veiller à la libération correcte de la mémoire de l'objet "enfant".
Cela aurait laissé une très grande liberté à l'utilisateur, mais cela présente un risque énorme : si l'utilisateur "fait une connerie", comme celles qui consistent à utiliser une variable temporaire comme enfant, à libérer trop tôt la mémoire allouée à un objet ou à oublier de le faire, tout le système s'écrase comme un château de cartes.
L'alternative est de se dire que le QWidget "parent" est finalement le mieux placé pour décider de quand ses enfants doivent être détruits.
Après tout, cela semble logique, vu que les enfants devront être détruits au moment où leur parent l'est si l'on veut éviter les problèmes.
Alors, évidemment, cela retire un peu de liberté à l'utilisateur, vu que ca le force à recourir à l'allocation dynamique, mais, d'un autre coté, cela lui apporte énormément de facilité (il n'a plus besoin de s'inquiéter de savoir quand les différents widgets deviennent inutiles) et de sécurité (il est sur que les widgets "enfants" resteront disponibles tant que le parent en aura besoin, tout en étant tout aussi sur qu'ils seront détruits "en temps et en heure").
C'est donc un compromis entre la liberté et la sécurité des plus acceptables (beaucoup plus, en tout cas, qu'un Patriot Act américain
)
Toute la mécanique Qt se base sur ce compromis et c'est en définitive très certainement la "moins mauvaise des solutions"
Partager