80 votes

Comment fonctionnent les méthodes delete et deleteLater par rapport aux signaux et slots dans Qt?

Il y a un objet de classe QNetworkReply. Il y a un slot (dans un autre objet) connecté à son signal finished(). Les signaux sont synchrones (ceux par défaut). Il n'y a qu'un seul fil.

À un moment donné, je veux me débarrasser des deux objets. Plus de signaux ou quoi que ce soit venant d'eux. Je veux les faire disparaître. Eh bien, je pensais, j'utiliserai

delete obj1; delete obj2;

Mais est-ce que je peux vraiment le faire? Les spécifications de ~QObject disent :

Supprimer un QObject alors que des événements sont en attente de traitement peut provoquer un crash.

Quels sont les 'événements en attente'? Est-ce que cela signifie que pendant que j'appelle mon delete, il y a déjà des 'événements en attente' à traiter et qu'ils pourraient causer un crash et je ne peux pas vraiment vérifier s'il y en a?

Donc disons que j'appelle :

obj1->deleteLater(); obj2->deleteLater();

Pour être sûre.

Mais, suis-je vraiment en sécurité? Le deleteLater ajoute un événement qui sera géré dans la boucle principale lorsque le contrôle arrivera là. Est-ce qu'il peut y avoir des événements en attente (signaux) pour obj1 ou obj2 déjà là, attendant d'être traités dans la boucle principale avant que deleteLater ne soit traité? Ce serait très malheureux. Je ne veux pas écrire du code vérifiant l'état 'quelque peu supprimé' et ignorant le signal entrant dans tous mes slots.

4 votes

Il semble que obj->disconnect(); obj->deleteLater(); soit la bonne solution :

1 votes

Ayant lu la source QObject, il semble que deleteLater() se contente d'envoyer un QDeferredDeleteEvent à l'objet sur lequel deleteLater() a été invoqué. Lorsque cet événement est reçu par le QObject, son gestionnaire d'événements invoquera ultimement delete régulier qui à son tour appelle le destructeur du QObject. La déconnexion du signal ne se produit qu'à la fin du destructeur, donc je suppose que le QObject exécutera des slots qui sont invoqués par des signaux de type DirectConnection qui sont émis après l'appel à deleteLater() mais avant que la boucle d'événements ne se termine.

74voto

Frank Osterfeld Points 13125

Supprimer les QObjects est généralement sûr (c'est-à-dire dans la pratique normale ; il pourrait y avoir des cas pathologiques dont je ne suis pas conscient pour le moment), si vous suivez deux règles de base :

  • Ne supprimez jamais un objet dans un slot ou une méthode qui est appelé directement ou indirectement par un signal (de type connexion "directe" synchrone) de l'objet à supprimer. Par exemple, si vous avez une classe Operation avec un signal Operation::finished() et un slot Manager::operationFinished(), vous ne souhaitez pas supprimer l'objet operation qui a émis le signal dans ce slot. La méthode émettant le signal finished() pourrait continuer à accéder à "this" après l'émission (par exemple, en accédant à un membre), puis opérer sur un pointeur "this" invalide.

  • De même, ne supprimez jamais un objet dans du code qui est appelé de manière synchrone par le gestionnaire d'événements de l'objet. Par exemple, ne supprimez pas un SomeWidget dans son SomeWidget::fooEvent() ou dans des méthodes/slots que vous appelez à partir de là. Le système d'événements continuera à fonctionner sur l'objet déjà supprimé -> Crash.

Les deux peuvent être difficiles à suivre, car les traces de pile ressemblent généralement à quelque chose de bizarre (comme un crash lors de l'accès à une variable membre POD), surtout lorsque vous avez des chaînes de signaux/slots compliquées où une suppression peut se produire plusieurs étapes en aval, initialement déclenchée par un signal ou un événement de l'objet supprimé.

Ces cas sont le cas d'utilisation le plus courant pour deleteLater(). Cela garantit que l'événement en cours peut être terminé avant que le contrôle ne retourne à la boucle d'événements, qui supprime ensuite l'objet. Une autre façon, que je trouve souvent meilleure, est de différer toute l'action en utilisant une connexion mise en file d'attente/QMetaObject::invokeMethod(..., Qt::QueuedConnection).

1 votes

Un exemple de ce crash serait: lors de l'événement focusOut d'un widget, je supprime certains widgets enfants. Le focus out est déclenché par un clic dans l'un des widgets à supprimer. Dans cet exemple, la suppression n'est pas sûre car lorsque la boucle d'événements est atteinte, l'objet n'existe déjà plus et provoque un crash lorsqu'il tente de délivrer un événement de clic à ce widget. deleteLater est sûr car l'objet est marqué pour la suppression et la boucle d'événements sait que cet événement ne doit pas être délivré car l'objet a déjà été supprimé.

22voto

liaK Points 6045

Les deux prochaines lignes de vos documents de référence disent la réponse.

De ~QObject,

Supprimer un QObject alors que des événements en attente doivent être livrés peut provoquer un crash. Vous ne devez pas supprimer directement le QObject s'il se trouve dans un thread différent de celui en cours d'exécution. Utilisez plutôt deleteLater(), qui fera en sorte que la boucle d'événements supprime l'objet une fois que tous les événements en attente lui auront été livrés.

Il nous est spécifiquement recommandé de ne pas le supprimer depuis d'autres threads. Étant donné que vous avez une application à un seul thread, il est sûr de supprimer QObject.

Autrement, si vous devez le supprimer dans un environnement multi-thread, utilisez deleteLater() qui supprimera votre QObject une fois que le traitement de tous les événements aura été effectué.

6 votes

Que dire de mon deuxième scénario ? Est-ce que les emplacements dans un objet peuvent encore être appelés après que j'ai appelé deleteLater sur celui-ci ?

14voto

Piotr Dobrogost Points 14412

Vous pouvez trouver la réponse à votre question en lisant à propos de l'un des Règles des Objets Delta qui dit ceci :

Sûr Signal (SS).
Il doit être sûr d'appeler des méthodes sur l'objet, y compris le destructeur, à partir d'un slot appelé par l'un de ses signaux.

Fragment :

Au cœur de QObject, il est possible d'être supprimé tout en émettant des signaux. Pour en profiter, il suffit de s'assurer que votre objet ne tente pas d'accéder à l'une de ses propres propriétés après avoir été supprimé. Cependant, la plupart des objets Qt ne sont pas écrits de cette manière, et il n'y a pas non plus d'exigence pour qu'ils le soient. Pour cette raison, il est recommandé d'appeler toujours deleteLater() si vous devez supprimer un objet pendant l'un de ses signaux, car il est probable que la méthode 'delete' provoquera simplement un crash de l'application.

Malheureusement, il n'est pas toujours clair quand vous devriez utiliser 'delete' vs deleteLater(). Autrement dit, il n'est pas toujours évident qu'un chemin de code a un source de signal. Souvent, vous pourriez avoir un bloc de code qui utilise 'delete' sur certains objets qui est sûr aujourd'hui, mais à un moment donné à l'avenir ce même bloc de code finit par être invoqué à partir d'une source de signal et maintenant soudainement votre application plante. La seule solution générale à ce problème est d'utiliser deleteLater() tout le temps, même si à première vue cela semble inutile.

En général, je considère les Règles des Objets Delta comme une lecture obligatoire pour chaque développeur Qt. C'est un excellent matériel de lecture.

1 votes

Si vous suivez le lien vers DOR, vous devez suivre les liens sur cette page pour plus de lecture, par exemple suivez le lien vers 'Signal Safe.' La première page à partir du lien est difficile à comprendre sans contexte. (Je suis en train de traquer un crash à la sortie en utilisant PyQt sur Windows, mon application ne supprime même pas d'objets, mais j'espère que le lien vers DOR offrira des perspectives.)

4voto

thorsten müller Points 3518

À ma connaissance, ce problème se pose principalement si les objets existent dans des threads différents. Ou peut-être pendant que vous traitez réellement les signaux.

Sinon, supprimer un QObject déconnectera d'abord tous les signaux et les slots et supprimera tous les événements en attente. Comme le ferait un appel à disconnect().

0voto

foogt Points 1

Même deleteLater peut ne pas être suffisant.

Imaginez que la boucle d'événements de votre thread principal est en train de traiter un événement/slot à l'aide de l'objet X, mais qu'elle retourne à la boucle d'événements (Cela peut arriver lors de l'émission de signaux/en attente/en pause avec une interface défectueuse de Qt). Le thread principal continue de traiter un autre événement qui supprime ou demande la suppression ultérieure du parent de X. Si c'est deleteLater, il est toujours possible que le thread principal retourne à la boucle d'événements puis supprime le parent de X.

Lorsque le parent de X est supprimé, X est également supprimé. Cela provoquerait le retour du thread principal à l'événement/slot précédent qu'il était en train de traiter en partie, et causerait un crash lors de la tentative d'utilisation de l'objet X supprimé.

Je souligne simplement que deleteLater n'est pas suffisant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X