Antes de comenzar, es preciso aclarar que las Señales y Ranuras de las que se habla en esta publicación se refieren a las creadas por usuarios, que son distintas a las señales del sistema operativo incluidas en std::signal1, tales como SIGSEGV (Acceso de memoria inválido; como fallo de segmentación) o SIGFPE (Operación aritmética errónea; como división por cero).

Qué son

Señales y Ranuras se refiere a una funcionalidad para comunicación entre objetos. En C++ es un término acuñado a Qt2 enfocado en la comunicación en interfaces gráficas. También es visto como una funcionalidad para desacoplar un remitente (la señal) de sus destinatarios (las ranuras).

Una señal es un objeto que puede informar a otros cuando algo en su estado interno es modificado. De manera más general, se refiere a la notificación de que algo ocurrió en el sistema, representa la ocurrencia de alguna acción definida.

Una característica importante de las señales es que pueden ser rastreables, es decir, que otros objetos pueden enterarse cuando una señal es emitida siempre y cuando la estén observando. Pueden publicar una información determinada.

Por otro lado, una ranura es una funcionalidad que puede ejecutarse cuando una señal se conecta a ella. Puede ser un método ordinario, una función libre, una expresión lambda.

Otros conceptos relevantes son los de Activación y Conexión; activación se refiere a cuando una señal es emitida y la ranura conectada es invocada; una conexión es la asociación entre una señal y una ranura; puede haber múltiples conexiones entre señales y ranuras, es decir, varias ranuras pueden estar conectadas a una señal, y varias señales pueden tener conexión a una ranura, teniendo en cuenta que cada conexión es independiente.

La señal es observable, la ranura es el observador. La señal gestiona la conexión, la ranura ejecuta la funcionalidad requerida.

Para qué sirven

Las Señales y Ranuras se usan comúnmente como concepto para formar el patrón Observador, para comunicación asíncrona de entrada-salida (io). Este patrón es ampliamente usado en la comunicación entre interfaces gráficas y sus funcionalidades; por ejemplo, el procesamiento que debería ejecutarse al presionar un botón en la interfaz gráfica.

Suponiendo que se tiene una pantalla con un botón para guardar un documento, al presionar el botón de Guardar se emite una señal; esa señal está previamente conectada a una ranura que ejecuta los procesos requeridos para tomar el documento, elegir la forma de almacenamiento, y realizar el guardado.

Bibliotecas para manejo de señales

Algunas de las bibliotecas destacadas para el manejo de señales en C++ son:

Biblioteca Paquete
libsigc++ sigc++-3
Boost.Signals2 Boost
signals-light signals-light

En Boost, la biblioteca de señales (Signals2) es una implementación de un sistema de señales y ranuras administradas. Las señales representan los objetos de devolución de llamadas (callbacks), editores (publishers), o eventos. Las ranuras representan los callback receivers, subscriptores (subscribers), u objetivos de los eventos, los cuales son invocados cuando la señal es emitida. La precede Signals1 que fue marcada como obsoleta, ya que no funciona correctamente en aplicaciones multi-hilo.

Ejemplo

Un ejemplo simple que funciona con las tres bibliotecas mencionadas es una conexión sencilla de una señal con dos ranuras:

#include <boost/signals2.hpp>
#include <print>

static void onPrint(const std::string& message) { 
  std::print("{}\n", message);
}

int main() {
  boost::signals2::signal<void(const std::string&)> printTextSignal;

  const auto slot1 = printTextSignal.connect([](const auto& name) { onPrint(name); });
  const auto slot2 = printTextSignal.connect(&onPrint);

  printTextSignal("Hola Boost.Signals2");

  slot1.disconnect();
  slot2.disconnect();
  
  printTextSignal("Adiós Boost.Signals2");

  return 0;
}

El resultado es la ejecución de las dos ranuras cuando se emite la señal:

Hola Boost.Signals2
Hola Boost.Signals2

La función onPrint es la ranura; por sí sola es una función común.
printTextSignal es un objeto invocable tipo señal (en este caso de la biblioteca de Boost.Signals2) que acepta como argumento una std::string y retorna void.
slot1 y slot2 son las conexiones de la señal a dos ranuras que invocan la función onPrint.
El llamado a printTextSignal con el argumento de texto emite la señal, por lo que las dos ranuras se enteran y ejecutan std::print para el mensaje contenido.
Finalmente, las dos conexiones son finalizadas; futuros llamados a printTextSignal no ejecutan la funcionalidad dado que ya no hay conexión, por lo cual no se imprime el segundo mensaje Adiós Boost.Signals2.

Con libsigcpp es bastante similar; la emisión de la señal es explícitamente nombrada:

#include <sigc++/sigc++.h>
//...
  sigc::signal<void(const std::string&)> printTextSignal;
  const auto slot1 = printTextSignal.connect(sigc::ptr_fun(&onPrint));
  printTextSignal.emit("Hola sigc++-3");
//...
Hola sigc++-3

Con signals-light se puede hacer un conteo de las ranuras conectadas

#include <signals_light/signal.hpp>
//...

  sl::Signal<void(const std::string&)> printTextSignal;
  const auto slot1 = printTextSignal.connect([](const auto& name){onPrint(name);});
  const auto slot2 = printTextSignal.connect(&onPrint);
  printTextSignal("Hola signals-light");
  std::print("Ranuras conectadas: {}", printTextSignal.slot_count());
//...
Hola signals-light
Hola signals-light
Ranuras conectadas: 2

Deja un comentario