tutorial signal custom and c++ qt signals-slots

c++ - signal - Apilar la señal Qt y el parámetro como referencia



qt signals and slots tutorial (4)

No, no encontrarás una referencia que cuelga. Al menos, no a menos que su ranura haga el tipo de cosas que también podrían causar problemas en las funciones normales.

Qt :: DirectionConnection

En general, podemos aceptar que esto no será un problema para las conexiones directas, ya que esas ranuras se llaman de inmediato. Su señal de emisión se bloquea hasta que se hayan llamado todas las ranuras. Una vez que eso sucede, emit myQtSignal(fooStackObject); volverá al igual que una función regular. De hecho, myQtSignal(fooStackObject); ¡Es una función regular! La palabra clave emit es completamente para su beneficio, no hace nada. La función de señal es especial porque su código es generado por el compilador de Qt: el moc .

Qt :: QueuedConnection

Benjamin T ha señalado en la documentación que los argumentos se copian, pero creo que es esclarecedor explorar cómo funciona esto bajo el capó (al menos en Qt 4).

Si empezamos por compilar nuestro proyecto y buscar nuestro archivo moc generado, podemos encontrar algo como esto:

// SIGNAL 0 void Test::myQtSignal(const FooObject & _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); }

Básicamente, le pasamos una serie de cosas a QMetaObject::activate : nuestro QObject, el metaObject para el tipo de nuestro QObject, nuestro identificador de señal y un puntero a cada uno de los argumentos que recibió nuestra señal.

Si investigamos QMetaObject::activate , encontraremos que está declarado en qobject.cpp . Esto es algo integral a cómo funcionan los QObjects. Después de examinar algunas cosas que son irrelevantes para esta pregunta, encontramos el comportamiento de las conexiones en cola. Esta vez llamamos a QMetaObject::queued_activate con nuestro QObject, el índice de la señal, un objeto que representa la conexión de la señal a la ranura y los argumentos nuevamente.

if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)) { queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv); continue;

Habiendo llegado a queued_activate, finalmente hemos llegado a la verdadera fuente de la pregunta.

Primero, construye una lista de tipos de conexión a partir de la señal:

QMetaMethod m = sender->metaObject()->method(signal); int *tmp = queuedConnectionTypes(m.parameterTypes());

Lo importante en queuedConnectionTypes es que usa QMetaType::type(const char* typeName) para obtener el id del metatipo del tipo de argumento de la firma de la señal. Esto significa dos cosas:

  1. El tipo debe tener un ID de QMetaType, por lo tanto, debe haberse registrado con qRegisterMetaType .

  2. Los tipos están normalized . Esto significa que "const T &" y "T" se asignan a la ID de QMetaType para T.

Finalmente, queued_activate pasa los tipos de argumentos de señal y los argumentos de señal dados a QMetaType::construct para copiar-construir nuevos objetos con QMetaType::construct vida que durarán hasta que la ranura se haya llamado en otro hilo. Una vez que el evento ha sido puesto en cola, la señal vuelve.

Y esa es básicamente la historia.

¿Puedo tener una "referencia pendiente" con el siguiente código (en una ranura eventual conectada a myQtSignal)?

class Test : public QObject { Q_OBJECT signals: void myQtSignal(const FooObject& obj); public: void sendSignal(const FooObject& fooStackObject) { emit myQtSignal(fooStackObject); } }; void f() { FooObject fooStackObject; Test t; t.sendSignal(fooStackObject); } int main() { f(); std::cin.ignore(); return 0; }

Particularmente si emit y slot no se ejecutan en el mismo hilo.


Si el ámbito en el que existe un objeto finaliza y luego se utiliza, se referirá a un objeto destruido que causará un comportamiento indefinido. Si no está seguro de si el alcance finalizará, es mejor asignar el objeto en la tienda gratuita a través de un new y usar algo como shared_ptr para administrar su vida útil.


Siento continuar con un tema de años, pero surgió en Google. Quiero aclarar la respuesta de HostileFork, ya que puede confundir a los futuros lectores.

Pasar una referencia a una señal Qt no es peligroso gracias a la forma en que funcionan las conexiones de señal / ranura:

  • Si la conexión es directa, las ranuras conectadas se llaman directamente, por ejemplo, cuando emit MySignal(my_string) devuelve todas las ranuras conectadas directamente que se han ejecutado.
  • Si la conexión está en cola, Qt crea una copia de las referencias . Entonces, cuando se llama a la ranura, tiene su propia copia válida de las variables pasadas por referencia. Sin embargo, esto significa que los parámetros deben ser de un tipo que Qt conozca para copiarlos.

http://qt-project.org/doc/qt-5.1/qtcore/qt.html#ConnectionType-enum


ACTUALIZACIÓN 20-ABR-2015

Originalmente creía que pasar una referencia a un objeto asignado en la pila sería equivalente a pasar la dirección de ese objeto. Por lo tanto, en ausencia de un envoltorio que almacene una copia (o un puntero compartido) , una conexión de ranura en cola podría terminar usando los datos erróneos.

Pero fue llamado a mi atención por @BenjaminT y @cgmb que Qt realmente tiene un manejo especial para los parámetros de referencia const. Llamará al constructor de copia y guardará el objeto copiado para usarlo en las llamadas a la ranura. Incluso si el objeto original que usted pasó se destruyó en el momento en que se ejecuta la ranura, las referencias que obtengan las ranuras serán a objetos diferentes por completo.

Puedes leer la respuesta de @cgmb para los detalles mecánicos. Pero aquí hay una prueba rápida:

#include <iostream> #include <QCoreApplication> #include <QDebug> #include <QTimer> class Param { public: Param () {} Param (Param const &) { std::cout << "Calling Copy Constructor/n"; } }; class Test : public QObject { Q_OBJECT public: Test () { for (int index = 0; index < 3; index++) connect(this, &Test::transmit, this, &Test::receive, Qt::QueuedConnection); } void run() { Param p; std::cout << "transmitting with " << &p << " as parameter/n"; emit transmit(p); QTimer::singleShot(200, qApp, &QCoreApplication::quit); } signals: void transmit(Param const & p); public slots: void receive(Param const & p) { std::cout << "receive called with " << &p << " as parameter/n"; } };

... y un principal:

#include <QCoreApplication> #include <QTimer> #include "param.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // name "Param" must match type name for references to work (?) qRegisterMetaType<Param>("Param"); Test t; QTimer::singleShot(200, qApp, QCoreApplication::quit); return a.exec(); }

Ejecutar esto demuestra que para cada una de las 3 conexiones de ranura, se realiza una copia separada del Param a través del constructor de copia:

Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor receive called with 0x1bbf7c0 as parameter receive called with 0x1bbf8a0 as parameter receive called with 0x1bbfa00 as parameter

Podría preguntarse qué bien hace "pasar por referencia" si Qt solo va a hacer copias de todos modos. Sin embargo, no siempre hace la copia ... depende del tipo de conexión. Si cambia a Qt::DirectConnection , no hace ninguna copia:

transmitting with 0x7ffebf241147 as parameter receive called with 0x7ffebf241147 as parameter receive called with 0x7ffebf241147 as parameter receive called with 0x7ffebf241147 as parameter

Y si cambia a pasar por valor , en realidad obtendrá más copias intermedias, especialmente en el caso Qt::QueuedConnection :

Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor receive called with 0x7fff15146ecf as parameter Calling Copy Constructor receive called with 0x7fff15146ecf as parameter Calling Copy Constructor receive called with 0x7fff15146ecf as parameter

Pero pasar por el puntero no hace ninguna magia especial. Así que tiene los problemas mencionados en la respuesta original, que voy a seguir a continuación. Pero ha resultado que el manejo de referencias es solo una bestia diferente.

RESPUESTA ORIGINAL

Sí, esto puede ser peligroso si su programa es multiproceso. Y es generalmente pobre estilo incluso si no es así. Realmente debería pasar objetos por valor sobre las conexiones de señal y ranura.

Tenga en cuenta que Qt admite "tipos compartidos implícitamente", por lo que pasar cosas como un QImage "por valor" no hará una copia a menos que alguien escriba al valor que recibe:

http://qt-project.org/doc/qt-5/implicit-sharing.html

El problema no tiene nada que ver fundamentalmente con señales y ranuras. C ++ tiene todo tipo de formas en que los objetos se pueden eliminar mientras se hace referencia a ellos, o incluso si parte de su código se está ejecutando en la pila de llamadas. Puede meterse en este problema fácilmente en cualquier código donde no tenga control sobre el código y no use la sincronización adecuada. Técnicas como usar QSharedPointer pueden ayudar.

Hay un par de cosas útiles que Qt ofrece para manejar con más gracia los escenarios de eliminación. Si hay un objeto que desea destruir pero está consciente de que podría estar en uso en este momento, puede usar el método QObject :: deleteLater ():

http://qt-project.org/doc/qt-5/qobject.html#deleteLater

Eso me ha sido útil un par de veces. Otra cosa útil es la señal QObject :: destroyed ():

http://qt-project.org/doc/qt-5/qobject.html#destroyed