II. Introduction à QScopedPointer▲
En général, Qt s'occupe de l'embarrassante partie d'allocation et désallocation. Soit par les conteneurs implicitement partagés, soit par le modèle de relations parent-enfant. Mais, de temps à autre, on doit allouer une variable sur le tas. Et là, le stress commence. Où la supprimer ? Comment être sûr de ne pas laisser une fuite de mémoire ?
Une solution est née : QScopedPointer. Il supprimera l'objet dès qu'il sera hors de portée.
void
foo()
{
QScopedPointer
<
int
>
i(new
int
(42
));
…
if
(someCondition)
return
; // XX
…
}
// … XX
L'entier pourra ici être supprimé à deux endroits, marqués par XX. Une nouvelle sortie dans notre code ne nous fera pas perdre la trace de cet entier : il sera automatiquement supprimé.
Comment accéder à l'objet pointé ? Deux opérateurs sont implémentés : operator* et operator->. On peut donc y accéder comme à tout pointeur.
QScopedPointer
<
int
>
i(new
int
(42
));
*
i =
43
;
Quelques opérateurs ne sont pas implémentés, cela est voulu par le design.
QScopedPointer
<
int
>
i(new
int
(42
));
i =
new
int
(43
); // Ne compilera pas
i.reset(new
int
(43
)); // Correct
L'opérateur d'assignation n'existe pas.
Nous avons pensé que reset() était assez effrayant pour que le lecteur réalise bien que l'objet pointé est bien détruit.
Pour la même raison, l'opérateur T* n'existe pas. Il permet de récupérer le contenu du pointeur. Cela évite les énormités comme celle-ci :
int
*
foo()
{
QScopedPointer
<
int
>
i(new
int
(42
));
…
return
i; // Heureusement, ceci ne compile pas
}
Voyez-vous l'erreur ?
Au moment de retourner, l'objet serait détruit, le pointeur sortant de son champ. Nous aurions retourné un pointeur bancal, menant potentiellement à un crash désagréable.
Cependant, nous pouvons dire à QScopedPointer que son travail est fini et prendre possession de l'objet pointé par la fonction take(). Nous pouvons en prendre parti pour réécrire la fonction précédente.
int
*
foo()
{
QScopedPointer
<
int
>
i(new
int
(42
));
…
if
(someError)
return
0
; // Notre entier est détruit ici
return
i.take(); // Dès maintenant, notre objet sur le tas existe de lui-même
}
D'autres problèmes apparaissent. Et la mémoire allouée avec malloc() ? Et l'opérateur new
[] pour les tableaux ?
Pour ces quelques cas a été introduit un second paramètre template qui définit le nettoyage.
QScopedPointer
<
int
, QScopedPointerPodDeleter
>
pod(static_cast
<
int
*>
(malloc(sizeof
int
)));
QScopedPointerPodDeleter
(POD : Plain Old Data, vieilles et simples données) appellera free() dès que l'objet sera hors de portée.
Il existe aussi QScopedArrayPointer
, qui appellera delete
[]. On propose aussi l'opérateur [] :
void
foo()
{
QScopedArrayPointer
<
int
>
i(new
int
[10
]);
i[2
] =
42
;
…
return
; // Le tableau d'entiers sera détruit par delete[]
}
Si l'objet voit ses références comptées, QExplicitelySharedDataPointer peut être utilisé pour s'assurer que l'objet sera détruit quand son compteur sera à 0.
QScopedPointer et QExplicitelySharedDataPointer sont actuellement très utilisés dans la branche S60, mais ils devraient se généraliser dans la branche principale. Avec l'introduction de ces pointeurs intelligents, on pourra enlever des tonnes de code peu lisible sans devenir aérien – vu que toutes les fonctions sont inlinées, le binaire final sera identique à l'approche basée sur les new et delete.
III. Un peu plus de pointages▲
Utiliser QScopedPointer au lieu de std::
auto_ptr ou de boost::
scoped_ptr, ce n'est pas le syndrome du pas-inventé-ici. Au tout début de nos discussions sur cette API, nous nous sommes aperçus que la suppression lors de la sortie de portée n'était pas suffisante, nous avions aussi besoin de gestionnaires de suppression personnalisés. Un exemple : QBrush, qui nécessite un peu de magie pour supprimer l'objet privé. Les autres classes de pointeurs n'offraient pas cette fonctionnalité, elles ont donc été évincées.
Pourquoi faire un argument template des suppresseurs personnalisés, et pas simplement un pointeur sur fonction membre ? Nous avons eu deux raisons. Premièrement, nous ne voulions pas la moindre pénalité en comparaison des new
et delete
. Un nouveau pointeur et son appel dans le destructeur auraient été très coûteux. Deuxièmement, à cause de notre politique de compatibilité binaire, la taille de la classe devait être sizeof
(void
*
). Ajouter un nouveau membre n'était pas possible.
Les nettoyeurs personnalisés peuvent être très utiles. Basiquement, on peut utiliser QScopedPointer pour tout nettoyage lors de la sortie de portée.
Toujours pas convaincu ? Regardez le code suivant, alors :
class
ForwardDeclared;
extern
ForwardDeclared *
myFunc();
…
std::
auto_ptr<
ForwardDeclared>
ptr(myFunc());
Il compile, mais ne s'exécute pas : vu que la classe n'est que déclarée en arrière, son destructeur ne sera pas appelé lors de la suppression ! QScopedPointer possède une protection : une erreur à la compilation en ce cas.
Pour s'assurer que vous puissiez écrire if
(myScopedPointer), nous avons dû introduire un opérateur bool
(). Mais cela aurait pu permettre l'écriture de int
x =
myScopedPointer;, à cause de la conversion implicite du booléen à l'entier. Heureusement, nous avons repéré ce problème lors d'une revue de l'API, ce dernier cas ne compilera pas.
Toujours pas convaincu ? Bonne nouvelle ! Tout le code est inliné, template, il n'y a donc pas de pénalité sur la taille de Qt ou sur le comportement à l'exécution si vous choisissez de les ignorer !
IV. L'article original▲
Les Qt Labs Blogs sont des blogs tenus par les développeurs de Qt, concernant les nouveautés, ou les utilisations un peu extrêmes du framework.
Nokia, Qt et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.
Cet article est la traduction de ces trois billets :
- Count with me: how many smart pointer classes does Qt have?, par Thiage Macieira ;
- Introducing QScopedPointer, par Harald Fernengel ;
- Some more pointing, par Harald Fernengel.
V. Remerciements▲
J'aimerais ici remercier yan et superjaja pour leur soutien dès le début ! Ainsi que RideKick pour sa relecture attentive !