En esta página

Señales y ranuras

Introducción

En programación GUI, cuando cambiamos un widget, a menudo queremos que otro widget sea notificado. En general, queremos que los objetos de cualquier tipo puedan comunicarse entre sí. Por ejemplo, si un usuario hace clic en un botón de Close, probablemente queramos que se llame a la función close() de la ventana.

Otros toolkits consiguen este tipo de comunicación utilizando callbacks. Una llamada de retorno es un puntero a una función, de modo que si quieres que una función de procesamiento te notifique algún evento, pasas un puntero a otra función (la llamada de retorno) a la función de procesamiento. La función de procesamiento llama entonces a la llamada de retorno cuando es apropiado. Aunque existen frameworks que utilizan este método con éxito, las retrollamadas pueden ser poco intuitivas y pueden sufrir problemas a la hora de garantizar la corrección tipográfica de los argumentos de las retrollamadas.

Señales y ranuras

En Qt, tenemos una alternativa a la técnica callback: Usamos señales y ranuras. Una señal se emite cuando ocurre un evento concreto. Los widgets de Qt tienen muchas señales predefinidas, pero siempre podemos subclasificar widgets para añadirles nuestras propias señales. Un slot es una función que se llama en respuesta a una señal particular. Los widgets de Qt tienen muchos slots predefinidos, pero es una práctica común subclasificar widgets y añadirles nuestros propios slots para poder manejar las señales que nos interesen.

Conexiones entre objetos

El mecanismo de señales y ranuras es de tipo seguro: La firma de una señal debe coincidir con la firma de la ranura receptora. (De hecho, una ranura puede tener una firma más corta que la señal que recibe porque puede ignorar argumentos extra). Dado que las firmas son compatibles, el compilador puede ayudarnos a detectar desajustes de tipo al utilizar la sintaxis basada en punteros de función. La sintaxis SIGNAL y SLOT basada en cadenas detectará las discrepancias de tipo en tiempo de ejecución. Las señales y las ranuras están poco acopladas: Una clase que emite una señal no sabe ni le importa qué ranuras reciben la señal. El mecanismo de señales y ranuras de Qt garantiza que si se conecta una señal a una ranura, ésta será llamada con los parámetros de la señal en el momento adecuado. Las señales y las ranuras pueden recibir cualquier número de argumentos de cualquier tipo. Son completamente seguras.

Todas las clases que heredan de QObject o de una de sus subclases (por ejemplo, QWidget) pueden contener señales y ranuras. Las señales son emitidas por los objetos cuando cambian su estado de una manera que puede ser interesante para otros objetos. Esto es todo lo que hace el objeto para comunicarse. No sabe ni le importa si algo está recibiendo las señales que emite. Se trata de una verdadera encapsulación de la información y garantiza que el objeto pueda utilizarse como componente de software.

Las ranuras pueden utilizarse para recibir señales, pero también son funciones miembro normales. Al igual que un objeto no sabe si algo recibe sus señales, una ranura no sabe si tiene alguna señal conectada a ella. Esto asegura que se puedan crear componentes verdaderamente independientes con Qt.

Se pueden conectar tantas señales como se desee a una sola ranura, y una señal puede conectarse a tantas ranuras como sea necesario. Incluso es posible conectar una señal directamente a otra señal. (Esto emitirá la segunda señal inmediatamente cuando se emita la primera).

Juntas, las señales y las ranuras constituyen un potente mecanismo de programación de componentes.

Señales

Las señales son emitidas por un objeto cuando su estado interno ha cambiado de alguna manera que puede ser interesante para el cliente o propietario del objeto. Las señales son funciones de acceso público y pueden emitirse desde cualquier lugar, pero recomendamos que sólo se emitan desde la clase que define la señal y sus subclases.

Cuando se emite una señal, las ranuras conectadas a ella suelen ejecutarse inmediatamente, como una llamada a una función normal. Cuando esto ocurre, el mecanismo de señales y ranuras es totalmente independiente de cualquier bucle de eventos de la interfaz gráfica de usuario. La ejecución del código que sigue a la sentencia emit se producirá una vez que todas las ranuras hayan retornado. La situación es ligeramente diferente cuando se utiliza queued connections; en tal caso, el código que sigue a la palabra clave emit continuará inmediatamente, y las ranuras se ejecutarán más tarde.

Si hay varias ranuras conectadas a una señal, las ranuras se ejecutarán una tras otra, en el orden en que se hayan conectado, cuando se emita la señal.

Las señales son generadas automáticamente por el moc y no deben implementarse en el archivo .cpp.

Una nota sobre los argumentos: Nuestra experiencia demuestra que las señales y las ranuras son más reutilizables si no utilizan tipos especiales. Si QScrollBar::valueChanged() utilizara un tipo especial como el hipotético QScrollBar::Range, sólo podría conectarse a ranuras diseñadas específicamente para QScrollBar. Conectar diferentes widgets de entrada sería imposible.

Ranuras

Una ranura es llamada cuando se emite una señal conectada a ella. Las ranuras son funciones C++ normales y se pueden llamar normalmente; su única característica especial es que se les pueden conectar señales.

Dado que las ranuras son funciones miembro normales, siguen las reglas normales de C++ cuando se invocan directamente. Sin embargo, como ranuras, pueden ser invocadas por cualquier componente, independientemente de su nivel de acceso, a través de una conexión señal-ranura. Esto significa que una señal emitida desde una instancia de una clase arbitraria puede hacer que una ranura privada sea invocada en una instancia de una clase no relacionada.

También se puede definir que las ranuras sean virtuales, lo que nos ha resultado muy útil en la práctica.

En comparación con los callbacks, las señales y los slots son ligeramente más lentos debido a la mayor flexibilidad que proporcionan, aunque la diferencia para aplicaciones reales es insignificante. En general, emitir una señal que está conectada a algunas ranuras, es aproximadamente diez veces más lento que llamar a los receptores directamente, con llamadas a funciones no virtuales. Esta es la sobrecarga requerida para localizar el objeto de conexión, para iterar de forma segura sobre todas las conexiones (es decir, comprobar que los receptores subsiguientes no han sido destruidos durante la emisión), y para marshall cualquier parámetro de forma genérica. Aunque diez llamadas a funciones no virtuales pueden parecer muchas, es mucha menos sobrecarga que cualquier operación de new o delete, por ejemplo. En cuanto realizas una operación de cadena, vector o lista que entre bastidores requiere new o delete, la sobrecarga de señales y ranuras sólo es responsable de una proporción muy pequeña de los costes completos de la llamada a la función. Lo mismo ocurre cuando se realiza una llamada al sistema en una ranura o se llama indirectamente a más de diez funciones. La simplicidad y flexibilidad del mecanismo de señales y ranuras bien vale la sobrecarga, que sus usuarios ni siquiera notarán.

Tenga en cuenta que otras bibliotecas que definen variables llamadas signals o slots pueden causar advertencias y errores en el compilador cuando se compilan junto a una aplicación basada en Qt. Para resolver este problema, #undef el símbolo del preprocesador infractor.

Un pequeño ejemplo

Una declaración de clase C++ mínima podría ser

class Counter
{
public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }
    void setValue(int value);

private:
    int m_value;
};

Una pequeña clase basada en QObject podría ser:

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT

// Note. The Q_OBJECT macro starts a private section.
// To declare public members, use the 'public:' access modifier.
public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }

public slots:
    void setValue(int value);

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};

La versión basada en QObject tiene el mismo estado interno, y proporciona métodos públicos para acceder al estado, pero además tiene soporte para la programación de componentes utilizando señales y ranuras. Esta clase puede decir al mundo exterior que su estado ha cambiado emitiendo una señal, valueChanged(), y tiene una ranura a la que otros objetos pueden enviar señales.

Todas las clases que contengan señales o ranuras deben mencionar Q_OBJECT al principio de su declaración. También deben derivar (directa o indirectamente) de QObject.

Las ranuras son implementadas por el programador de la aplicación. A continuación se muestra una posible implementación de la ranura Counter::setValue():

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}

La línea emit emite la señal valueChanged() desde el objeto, con el nuevo valor como argumento.

En el siguiente fragmento de código, creamos dos objetos Counter y conectamos la señal valueChanged() del primer objeto a la ranura setValue() del segundo mediante QObject::connect():

    Counter a, b;
    QObject::connect(&a, &Counter::valueChanged,
                     &b, &Counter::setValue);

    a.setValue(12);     // a.value() == 12, b.value() == 12
    b.setValue(48);     // a.value() == 12, b.value() == 48

La llamada a a.setValue(12) hace que a emita una señal valueChanged(12), que b recibirá en su ranura setValue(), es decir, se llama a b.setValue(12). Entonces b emite la misma señal valueChanged(), pero como no se ha conectado ninguna ranura a la señal valueChanged() de b, la señal se ignora.

Observe que la función setValue() establece el valor y emite la señal sólo si value != m_value. Esto evita bucles infinitos en el caso de conexiones cíclicas (por ejemplo, si b.valueChanged() estuviera conectado a a.setValue()).

Por defecto, por cada conexión realizada se emite una señal; en caso de conexiones duplicadas se emiten dos señales. Puedes romper todas estas conexiones con una sola llamada a disconnect(). Si pasa el Qt::UniqueConnection type , la conexión sólo se realizará si no es un duplicado. Si ya hay un duplicado (exactamente la misma señal a exactamente la misma ranura en los mismos objetos), la conexión fallará y connect devolverá false.

Este ejemplo ilustra que los objetos pueden trabajar juntos sin necesidad de conocer ninguna información sobre los demás. Para ello, basta con que los objetos estén conectados entre sí, lo que puede conseguirse con unas simples llamadas a la función QObject::connect() o con la función de conexiones automáticas de uic.

Un ejemplo real

A continuación se muestra un ejemplo de la cabecera de una clase widget sencilla sin funciones miembro. El propósito es mostrar cómo puedes utilizar señales y ranuras en tus propias aplicaciones.

#ifndef LCDNUMBER_H
#define LCDNUMBER_H

#include <QFrame>

class LcdNumber : public QFrame
{
    Q_OBJECT

LcdNumber hereda QObject, que tiene la mayor parte del conocimiento sobre señales y ranuras, a través de QFrame y QWidget. Es algo similar al widget incorporado QLCDNumber.

La macro Q_OBJECT es expandida por el preprocesador para declarar varias funciones miembro que son implementadas por moc; si obtienes errores de compilación del tipo "undefined reference to vtable for LcdNumber", probablemente has olvidado ejecutar el moc o incluir la salida del moc en el comando link.

public:
    LcdNumber(QWidget *parent = nullptr);

signals:
    void overflow();

Tras el constructor de la clase y los miembros de public, declaramos la clase signals. La clase LcdNumber emite una señal, overflow(), cuando se le pide que muestre un valor imposible.

Si no te importa el desbordamiento, o sabes que el desbordamiento no puede ocurrir, puedes ignorar la señal overflow(), es decir, no la conectes a ninguna ranura.

Si por el contrario quieres llamar a dos funciones de error diferentes cuando el número se desborde, simplemente conecta la señal a dos ranuras diferentes. Qt llamará a ambas (en el orden en que fueron conectadas).

public slots:
    void display(int num);
    void display(double num);
    void display(const QString &str);
    void setHexMode();
    void setDecMode();
    void setOctMode();
    void setBinMode();
    void setSmallDecimalPoint(bool point);
};

#endif

Una ranura es una función receptora utilizada para obtener información sobre cambios de estado en otros widgets. LcdNumber la utiliza, como indica el código anterior, para establecer el número mostrado. Dado que display() es parte de la interfaz de la clase con el resto del programa, la ranura es pública.

Varios de los programas de ejemplo conectan la señal valueChanged() de un QScrollBar a la ranura display(), para que el número del LCD muestre continuamente el valor de la barra de desplazamiento.

Tenga en cuenta que display() está sobrecargado. Las señales y ranuras de Qt te dan conexiones fuertemente tipadas. Son más seguras en tiempo de compilación que los callbacks tradicionales. Con callbacks, necesitarías diferentes nombres de función y seguimiento manual de tipos. Pero con las funciones sobrecargadas, necesitas especificar qué versión usar. La siguiente sección te muestra cómo.

Conexión a señales y ranuras sobrecargadas

Cuando las señales o ranuras están sobrecargadas (tienen múltiples versiones con diferentes parámetros), debe especificar explícitamente a qué versión desea conectarse utilizando la sintaxis de puntero de función. Puede utilizar qOverload() o static_cast para desambiguar:

// Connect to the int overload of QComboBox::currentIndexChanged(int)
connect(comboBox, qOverload<int>(&QComboBox::currentIndexChanged),
        this, &MyClass::handleIndexChanged);

// Or select QLCDNumber::display(int) when connecting from QSlider::valueChanged(int)
connect(slider, &QSlider::valueChanged,
        lcd, qOverload<int>(&QLCDNumber::display));

// Using static_cast (more verbose):
connect(comboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
        this, &MyClass::handleIndexChanged);

// Or using a lambda to call the correct overload:
connect(slider, &QSlider::valueChanged,
        this, [lcd](int value) { lcd->display(value); });

Gestión automática de conexiones

Qt gestiona automáticamente el tiempo de vida de las conexiones entre tipos derivados de QObject. Cuando se destruye el objeto emisor o receptor, la conexión se elimina automáticamente, evitando llamadas a objetos eliminados. Esto se aplica tanto a la sintaxis function-pointer como a la sintaxis SIGNAL/SLOT basada en cadenas.

Para las conexiones lambda, proporcione un objeto de contexto (normalmente this) para garantizar que la lambda se desconecta cuando se destruye el contexto:

connect(button, &QPushButton::clicked, this, [this]{ handleClick(); });

Aunque Qt protege contra la entrega de señales a objetos completamente destruidos, las señales aún pueden ser entregadas durante la destrucción del objeto después de que el destructor de una clase derivada haya terminado pero antes de llegar a ~QObject. Esta limitación se aplica específicamente a las conexiones realizadas con la sintaxis function-pointer. Puede ocurrir cuando un destructor de clase base emite señales a las que la clase derivada ya destruida estaba conectada. Considera desconectar explícitamente tales señales en los destructores si esto puede ser problemático para tu clase.

Señales y ranuras con argumentos por defecto

Las firmas de señales y ranuras pueden contener argumentos, y los argumentos pueden tener valores por defecto. Considere QObject::destroyed():

void destroyed(QObject* = nullptr);

Cuando se borra un QObject, emite esta señal QObject::destroyed(). Queremos capturar esta señal, dondequiera que tengamos una referencia colgante al QObject eliminado, para poder limpiarla. Una firma de ranura adecuada podría ser:

void objectDestroyed(QObject* obj = nullptr);

Para conectar la señal a la ranura, usamos QObject::connect(). Hay varias formas de conectar señales y ranuras. La primera es utilizar punteros de función:

connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

Utilizar QObject::connect() con punteros de función tiene varias ventajas. En primer lugar, permite al compilador comprobar que los argumentos de la señal son compatibles con los argumentos de la ranura. Los argumentos también pueden ser convertidos implícitamente por el compilador, si es necesario.

También puede conectarse a functors o lambdas C++11:

connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });

En ambos casos, proporcionamos this como contexto en la llamada a connect(). El objeto context proporciona información sobre en qué hilo debe ejecutarse el receptor. Esto es importante, ya que proporcionar el contexto asegura que el receptor se ejecute en el hilo de contexto.

La lambda será desconectada cuando el emisor o el contexto sean destruidos. Debes tener cuidado de que cualquier objeto utilizado dentro del functor siga vivo cuando se emita la señal.

La otra forma de conectar una señal a una ranura es utilizar QObject::connect() y las macros SIGNAL y SLOT. La regla sobre si incluir argumentos o no en las macros SIGNAL() y SLOT(), si los argumentos tienen valores por defecto, es que la firma pasada a la macro SIGNAL() no debe tener menos argumentos que la firma pasada a la macro SLOT().

Todas ellas funcionarían:

connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));

Pero ésta no funcionará:

connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));

...porque la ranura estará esperando un QObject que la señal no enviará. Esta conexión informará de un error de ejecución.

Tenga en cuenta que los argumentos de señal y ranura no son comprobados por el compilador cuando se utiliza esta sobrecarga QObject::connect().

Uso avanzado de señales y ranuras

Para los casos en los que pueda necesitar información sobre el emisor de la señal, Qt proporciona la función QObject::sender(), que devuelve un puntero al objeto que envió la señal.

Las expresiones lambda son una forma práctica de pasar argumentos personalizados a una ranura:

connect(action, &QAction::triggered, engine,
        [=]() { engine->processAction(action->text()); });

Uso de Qt con señales y ranuras de terceros

Es posible usar Qt con un mecanismo de señales/ranuras de terceros. Incluso puedes usar ambos mecanismos en el mismo proyecto. Para ello, escriba lo siguiente en su archivo de proyecto CMake:

target_compile_definitions(my_app PRIVATE QT_NO_KEYWORDS)

En un archivo de proyecto qmake (.pro), necesitas escribir:

CONFIG += no_keywords

Indica a Qt que no defina las palabras clave moc signals, slots, y emit, porque estos nombres serán utilizados por una librería de terceros, por ejemplo Boost. Entonces para continuar usando las señales y ranuras de Qt con la bandera no_keywords, simplemente reemplaza todos los usos de las palabras clave moc de Qt en tus fuentes con las correspondientes macros de Qt Q_SIGNALS (o Q_SIGNAL), Q_SLOTS (o Q_SLOT), y Q_EMIT.

Señales y ranuras en las bibliotecas basadas en Qt

La API pública de las bibliotecas basadas en Qt debería utilizar las palabras clave Q_SIGNALS y Q_SLOTS en lugar de signals y slots. De lo contrario, es difícil utilizar una biblioteca de este tipo en un proyecto que defina QT_NO_KEYWORDS.

Para imponer esta restricción, el creador de la biblioteca puede establecer la definición del preprocesador QT_NO_SIGNALS_SLOTS_KEYWORDS al construir la biblioteca.

Esta definición excluye señales y ranuras sin afectar a la posibilidad de utilizar otras palabras clave específicas de Qt en la implementación de la biblioteca.

Ver también QLCDNumber, QObject::connect(), Meta-Object System, y Qt's Property System.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.