FAQ Qt
FAQ QtConsultez toutes les FAQ
Nombre d'auteurs : 26, nombre de questions : 298, dernière mise à jour : 15 juin 2021
Qt est basé sur une sémantique de copie, c'est-à-dire qu'elle utilise beaucoup la copie pour permettre de créer des logiciels robustes et évite un bon nombre de problèmes. Pour optimiser cela, Qt implémente le pattern COW (Copy on Write) : la copie interne est réellement effectuée lors d'un accès en écriture sur un objet interne partagé d'une classe. Dit d'une autre manière, Qt va partager en lecture un objet interne entre différentes instances identiques d'une classe.
- Lors d'une copie, un objet interne est partagé.
- Tant qu'aucune des instances ne modifie cet objet, il reste partagé.
- Dès qu'une des instances modifie l'objet, cette instance va copier l'objet interne et appliquer la modification.
// s1 va créer un objet interne contenant "hello word"
QString
s1 =
"hello word"
;
// s2 va référencer le même objet interne que s1.
// Il n'y a pas eu de vraie copie.
QString
s2 =
s1;
// Accès en lecture au premier caractère => inutile de faire une copie
qDebug
()<<
s2[0
];
// On veut modifier la première lettre de s2 :
// s2 créé un nouvel objet interne et copie le contenu.
// s1 et s2 n'utilisent plus le même objet interne
// s2 modifie la première lettre
s2[0
] =
'b'
;
Remarque : ce pattern est thread safe dans l'implémentation Qt.
Lien : Liste des classes Qt basées sur le COW
Lien : Comment optimiser la copie de ses classes ?
Qt est conçu de façon à rendre aisée la gestion de la mémoire. Il y a deux mécanismes pour y arriver : les types simples et les hiérarchies.
Les objets simples tels que les chaînes de caractères, conteneurs… peuvent être manipulés comme les types de base, la mémoire est nettoyée automatiquement par le destructeur.
QString
getSiteName()
{
// pas de problème pour l'allouer sur la pile
QString
siteName =
"Developpez.com"
;
// pas de problème pour l'utiliser comme type de retour
return
siteName;
}
Ce type d'objet n'utilise en interne qu'un pointeur vers un délégué, ce qui veut dire que la manipulation de ces objets est extrêmement rapide.
QRectF
rect(0.0
, 0.0
, 50.0
, 50.0
);
// le constructeur par copie est rapide
QRectF
rectClone =
rect;
QRectF
copy;
// l'affectation est tout aussi rapide
copy =
rect;
Ce type d'objet est donc très facile à utiliser car on peut l'employer comme s'il s'agissait d'un type de base.
D'autres objets ont besoin d'une gestion plus complexe de la mémoire car il faut contrôler précisément leur durée de vie. Pour ces objets, Qt a introduit le mécanisme de hiérarchie d'objets, qui est disponible pour toutes les sous-classes de QObject.
Les objets d'une hiérarchie peuvent avoir un objet parent, et des objets enfants (à ne pas confondre avec l'héritage, il s'agit ici d'encapsulation). Lorsqu'un objet est détruit, tous ses objets enfants sont détruits aussi.
Le parent est précisé dans le constructeur des objets ou grâce à la méthode QObject::setParent().
QString
giveString()
{
QObject
parent;
MyObjet *
myObjet =
new
MyObject(&
parent);
myObjet->
doSomeComputation();
return
myObjet->
getString();
}
Étonnamment, le code précédent n'a pas de fuite de mémoire. L'objet MyObjet, créé sur le tas, a, comme parent, le QObject "parent". Lorsque le parent arrive à la fin de la fonction giveString(), son destructeur se charge de supprimer l'objet myObjet.
L'exemple précédent est artificiel, voyons un exemple plus réaliste.
void
showMessage()
{
QDialog
dialog;
QVBoxLayout
*
layout =
new
QVBoxLayout
(&
dialog);
QLabel
*
message =
new
QLabel
("Cliquez sur le bouton"
);
layout->
addWidget(message);
QButton
*
button =
new
QButton
("Cliquez ici"
);
layout->
addWidget(button);
dialog.exec();
}
Quelle magie fait que ce code fonctionne ? Lorsqu'un widget est ajouté au layout, le layout le reparente automatiquement avec son widget propriétaire (dialog dans ce cas-ci). Finalement, lorsque la fonction se termine, le destructeur de dialog supprime tous les objets et nettoie la mémoire.
Une majeure partie de Qt est basée sur le COW. Il permet ainsi d'utiliser ce pattern. Pour cela, il faut créer une classe qui hérite de QSharedData, et qui possède un constructeur, un constructeur par copie et un destructeur public. Cette classe sera l'objet interne qui sera partagé. Elle possède un compteur de références thread safe et ne doit pas être directement accédée. Sa vie sera gérée par d'autres classes.
Pour accéder à une instance de cette classe, deux choix sont possibles :
- QSharedDataPointer : permet de partager implicitement un QSharedData. L'objet interne est partagé en lecture. L'accès à l'objet en écriture va générer une copie de l'objet ;
- QExplicitlySharedDataPointer : permet de partager explicitement un QSharedData. L'objet interne est partagé en lecture/écriture. L'objet interne sera copié uniquement sur demande.
Ces deux classes sont des pointeurs intelligents spécialisés dans la manipulation des pointeurs sur QSharedData. Ils implémentent donc la sémantique des pointeurs avec des accès const (lecture) et non const (écriture). Elles détruiront le QSharedData une fois son compteur à zéro. Ces pointeurs intelligents possèdent deux fonctions qu'il est utile de connaître :
- detach() : si le compteur de référence est supérieur à 1, le QSharedData sera copié ;
- reset() : initialise à null le pointeur intelligent.
Remarque : ces classes sont thread safe.
Lien : Comment Qt optimise-t-il les copies ?
Lien : Faq C++ : pointeurs intelligentsFaq C++ : pointeurs intelligents
Lien : Tutoriel : Pointeurs intelligents