¿Cómo funciona delete y deleteLater en lo que respecta a señales y ranuras en Qt?

Hay un objeto de la clase QNetworkReply. Hay una ranura (en algún otro objeto) conectada a su señal terminada (). Las señales son sincrónicas (las predeterminadas). Solo hay un hilo

En algún momento del tiempo quiero deshacerme de ambos objetos. No más señales ni nada de ellos. Quiero que se vayan. Bueno, pensé, usaré

delete obj1; delete obj2; 

¿Pero puedo realmente? Las especificaciones para ~ QObject dicen:

Eliminar un QObject mientras los eventos pendientes están esperando ser entregados puede causar un locking.

¿Cuáles son los ‘eventos pendientes’? ¿Podría eso significar que mientras estoy llamando a mi delete , ya hay algunos ‘eventos pendientes’ que se entregarán y que pueden causar un locking y realmente no puedo verificar si hay alguno?

Entonces, digamos que llamo:

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

Para estar seguro.

Pero, ¿estoy realmente seguro? deleteLater agrega un evento que se manejará en el ciclo principal cuando llegue el control. ¿Puede haber algunos eventos pendientes (señales) para obj1 u obj2 ya están allí, esperando a ser manejados en el ciclo principal antes de que se procese deleteLater? Eso sería muy desafortunado. No quiero escribir código para verificar el estado “algo borrado” e ignorar la señal entrante en todas mis ranuras.

La eliminación de QObjects generalmente es segura (es decir, en la práctica normal; puede haber casos patológicos que no conozco en atm), si sigues dos reglas básicas:

  • Nunca elimine un objeto en una ranura o método que sea llamado directa o indirectamente por una señal (sincrónica, tipo de conexión “directa”) del objeto que se eliminará. Por ejemplo, si tiene una operación de clase con una operación de operación :: finished () y una ranura Manager :: operationFinished (), no desea eliminar el objeto de operación que emitió la señal en esa ranura. El método que emite la señal finished () podría continuar accediendo a “this” después de la emisión (por ejemplo, acceder a un miembro), y luego operar con un puntero “this” inválido.

  • Del mismo modo, nunca elimine un objeto en el código que se llama sincrónicamente desde el controlador de eventos del objeto. Por ejemplo, no elimine un SomeWidget en su SomeWidget :: fooEvent () o en los métodos / slots que llama desde allí. El sistema de eventos continuará funcionando en el objeto ya eliminado -> Bloquear.

Ambas pueden ser difíciles de rastrear, ya que las trazas inversas suelen parecer extrañas (como locking al acceder a una variable miembro POD), especialmente cuando tienes cadenas de señal / ranura complicadas donde una eliminación puede ocurrir varios pasos iniciados originalmente por una señal o evento de el objeto que se elimina

Tales casos son el caso de uso más común para deleteLater (). Se asegura de que el evento actual se pueda completar antes de que el control vuelva al bucle de evento, que luego elimina el objeto. Otra, me parece que a menudo es mejor diferir toda la acción utilizando una conexión en cola / QMetaObject :: invokeMethod (…, Qt :: QueuedConnection).

Las siguientes dos líneas de sus documentos referidos dicen la respuesta.

Desde ~ QObject ,

Eliminar un QObject mientras los eventos pendientes están esperando ser entregados puede causar un locking. No debe eliminar el QObject directamente si existe en un subproceso diferente al que se está ejecutando actualmente. Utilice deleteLater () en su lugar, lo que hará que el bucle de evento elimine el objeto después de que se le hayan entregado todos los eventos pendientes.

Específicamente nos dice que no eliminemos de otros hilos. Como tiene una sola aplicación con hilos, es seguro eliminar QObject .

De lo contrario, si tiene que eliminarlo en un entorno de subprocesos múltiples, use deleteLater() que borrará su QObject una vez que se haya procesado todos los eventos.

Puede encontrar la respuesta a su pregunta leyendo acerca de una de las Reglas de objeto Delta que dice esto:

Signal Safe (SS).
Debe ser seguro llamar a métodos en el objeto, incluido el destructor, desde dentro de una ranura a la que llama una de sus señales.

Fragmento:

En esencia, QObject admite que se borre al señalizar. Para aprovecharlo, solo tienes que asegurarte de que tu objeto no intente acceder a ninguno de sus miembros una vez que se haya eliminado. Sin embargo, la mayoría de los objetos Qt no están escritos de esta manera, y tampoco es obligatorio que sean. Por esta razón, se recomienda que siempre llame a deleteLater () si necesita eliminar un objeto durante una de sus señales, porque las probabilidades son que ‘borrar’ simplemente bloqueará la aplicación.

Desafortunadamente, no siempre está claro cuándo debes usar ‘eliminar’ frente a eliminar Más (). Es decir, no siempre es obvio que una ruta de código tiene una fuente de señal. A menudo, puede tener un bloque de código que usa ‘eliminar’ en algunos objetos que es seguro hoy, pero en algún punto en el futuro este mismo bloque de código termina siendo invocado desde una fuente de señal y ahora de repente su aplicación se bloquea. La única solución general a este problema es usar deleteLater () todo el tiempo, incluso si a simple vista parece innecesario.

En general, considero que Delta Object Rules es obligatoria para todos los desarrolladores de Qt. Es un excelente material de lectura.

Hasta donde yo sé, esto es principalmente un problema si los objetos existen en diferentes hilos. O tal vez mientras estás procesando las señales.

De lo contrario, al eliminar un QObject, primero se desconectarán todas las señales y ranuras y se eliminarán todos los eventos pendientes. Como lo haría una llamada a desconectar ().